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()
324
self.assertTrue(server._http_running)
327
self.assertFalse(server._http_running)
329
def test_create_http_server_one_zero(self):
330
class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
332
protocol_version = 'HTTP/1.0'
334
server = http_server.HttpServer(RequestHandlerOneZero)
335
self.start_server(server)
336
self.assertIsInstance(server._httpd, http_server.TestingHTTPServer)
338
def test_create_http_server_one_one(self):
339
class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
341
protocol_version = 'HTTP/1.1'
343
server = http_server.HttpServer(RequestHandlerOneOne)
344
self.start_server(server)
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')
355
self.start_server(server)
356
self.assertIsInstance(server._httpd,
357
http_server.TestingThreadingHTTPServer)
359
def test_create_http_server_force_one_zero(self):
360
class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
362
protocol_version = 'HTTP/1.1'
364
server = http_server.HttpServer(RequestHandlerOneOne,
365
protocol_version='HTTP/1.0')
366
self.start_server(server)
367
self.assertIsInstance(server._httpd,
368
http_server.TestingHTTPServer)
371
153
class TestWithTransport_pycurl(object):
372
154
"""Test case to inherit from if pycurl is present"""
828
653
self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
829
654
t.readv, 'a', [(12,2)])
831
def test_readv_multiple_get_requests(self):
832
server = self.get_readonly_server()
833
t = self._transport(server.get_url())
834
# force transport to issue multiple requests
835
t._max_readv_combine = 1
836
t._max_get_ranges = 1
837
l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
838
self.assertEqual(l[0], (0, '0'))
839
self.assertEqual(l[1], (1, '1'))
840
self.assertEqual(l[2], (3, '34'))
841
self.assertEqual(l[3], (9, '9'))
842
# The server should have issued 4 requests
843
self.assertEqual(4, server.GET_request_nb)
845
def test_readv_get_max_size(self):
846
server = self.get_readonly_server()
847
t = self._transport(server.get_url())
848
# force transport to issue multiple requests by limiting the number of
849
# bytes by request. Note that this apply to coalesced offsets only, a
850
# single range will keep its size even if bigger than the limit.
852
l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
853
self.assertEqual(l[0], (0, '0'))
854
self.assertEqual(l[1], (1, '1'))
855
self.assertEqual(l[2], (2, '2345'))
856
self.assertEqual(l[3], (6, '6789'))
857
# The server should have issued 3 requests
858
self.assertEqual(3, server.GET_request_nb)
860
def test_complete_readv_leave_pipe_clean(self):
861
server = self.get_readonly_server()
862
t = self._transport(server.get_url())
863
# force transport to issue multiple requests
865
l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
866
# The server should have issued 3 requests
867
self.assertEqual(3, server.GET_request_nb)
868
self.assertEqual('0123456789', t.get_bytes('a'))
869
self.assertEqual(4, server.GET_request_nb)
871
def test_incomplete_readv_leave_pipe_clean(self):
872
server = self.get_readonly_server()
873
t = self._transport(server.get_url())
874
# force transport to issue multiple requests
876
# Don't collapse readv results into a list so that we leave unread
877
# bytes on the socket
878
ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
879
self.assertEqual((0, '0'), ireadv.next())
880
# The server should have issued one request so far
881
self.assertEqual(1, server.GET_request_nb)
882
self.assertEqual('0123456789', t.get_bytes('a'))
883
# get_bytes issued an additional request, the readv pending ones are
885
self.assertEqual(2, server.GET_request_nb)
888
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
889
"""Always reply to range request as if they were single.
891
Don't be explicit about it, just to annoy the clients.
894
def get_multiple_ranges(self, file, file_size, ranges):
895
"""Answer as if it was a single range request and ignores the rest"""
896
(start, end) = ranges[0]
897
return self.get_single_range(file, file_size, start, end)
900
657
class TestSingleRangeRequestServer(TestRangeRequestServer):
901
658
"""Test readv against a server which accept only single range requests"""
903
_req_handler_class = SingleRangeRequestHandler
906
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
907
"""Only reply to simple range requests, errors out on multiple"""
909
def get_multiple_ranges(self, file, file_size, ranges):
910
"""Refuses the multiple ranges request"""
913
self.send_error(416, "Requested range not satisfiable")
915
(start, end) = ranges[0]
916
return self.get_single_range(file, file_size, start, end)
660
def create_transport_readonly_server(self):
661
return HttpServer(SingleRangeRequestHandler)
664
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
665
TestCaseWithWebserver):
666
"""Tests single range requests accepting server for urllib implementation"""
668
_transport = HttpTransport_urllib
671
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
672
TestSingleRangeRequestServer,
673
TestCaseWithWebserver):
674
"""Tests single range requests accepting server for pycurl implementation"""
919
677
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
920
678
"""Test readv against a server which only accept single range requests"""
922
_req_handler_class = SingleOnlyRangeRequestHandler
925
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
926
"""Ignore range requests without notice"""
929
# Update the statistics
930
self.server.test_case_server.GET_request_nb += 1
931
# Just bypass the range handling done by TestingHTTPRequestHandler
932
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
680
def create_transport_readonly_server(self):
681
return HttpServer(SingleOnlyRangeRequestHandler)
684
class TestSingleOnlyRangeRequestServer_urllib(TestSingleOnlyRangeRequestServer,
685
TestCaseWithWebserver):
686
"""Tests single range requests accepting server for urllib implementation"""
688
_transport = HttpTransport_urllib
691
class TestSingleOnlyRangeRequestServer_pycurl(TestWithTransport_pycurl,
692
TestSingleOnlyRangeRequestServer,
693
TestCaseWithWebserver):
694
"""Tests single range requests accepting server for pycurl implementation"""
935
697
class TestNoRangeRequestServer(TestRangeRequestServer):
936
698
"""Test readv against a server which do not accept range requests"""
938
_req_handler_class = NoRangeRequestHandler
941
class MultipleRangeWithoutContentLengthRequestHandler(
942
http_server.TestingHTTPRequestHandler):
943
"""Reply to multiple range requests without content length header."""
945
def get_multiple_ranges(self, file, file_size, ranges):
946
self.send_response(206)
947
self.send_header('Accept-Ranges', 'bytes')
948
boundary = "%d" % random.randint(0,0x7FFFFFFF)
949
self.send_header("Content-Type",
950
"multipart/byteranges; boundary=%s" % boundary)
952
for (start, end) in ranges:
953
self.wfile.write("--%s\r\n" % boundary)
954
self.send_header("Content-type", 'application/octet-stream')
955
self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
959
self.send_range_content(file, start, end - start + 1)
961
self.wfile.write("--%s\r\n" % boundary)
964
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
966
_req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
969
class TruncatedMultipleRangeRequestHandler(
970
http_server.TestingHTTPRequestHandler):
971
"""Reply to multiple range requests truncating the last ones.
973
This server generates responses whose Content-Length describes all the
974
ranges, but fail to include the last ones leading to client short reads.
975
This has been observed randomly with lighttpd (bug #179368).
700
def create_transport_readonly_server(self):
701
return HttpServer(NoRangeRequestHandler)
704
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
705
TestCaseWithWebserver):
706
"""Tests range requests refusing server for urllib implementation"""
708
_transport = HttpTransport_urllib
711
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
712
TestNoRangeRequestServer,
713
TestCaseWithWebserver):
714
"""Tests range requests refusing server for pycurl implementation"""
717
class TestLimitedRangeRequestServer(object):
718
"""Tests readv requests against server that errors out on too much ranges.
720
This MUST be used by daughter classes that also inherit from
721
TestCaseWithWebserver.
723
We can't inherit directly from TestCaseWithWebserver or the
724
test framework will try to create an instance which cannot
725
run, its implementation being incomplete.
978
_truncated_ranges = 2
980
def get_multiple_ranges(self, file, file_size, ranges):
981
self.send_response(206)
982
self.send_header('Accept-Ranges', 'bytes')
984
self.send_header('Content-Type',
985
'multipart/byteranges; boundary=%s' % boundary)
986
boundary_line = '--%s\r\n' % boundary
987
# Calculate the Content-Length
989
for (start, end) in ranges:
990
content_length += len(boundary_line)
991
content_length += self._header_line_length(
992
'Content-type', 'application/octet-stream')
993
content_length += self._header_line_length(
994
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
995
content_length += len('\r\n') # end headers
996
content_length += end - start # + 1
997
content_length += len(boundary_line)
998
self.send_header('Content-length', content_length)
1001
# Send the multipart body
1003
for (start, end) in ranges:
1004
self.wfile.write(boundary_line)
1005
self.send_header('Content-type', 'application/octet-stream')
1006
self.send_header('Content-Range', 'bytes %d-%d/%d'
1007
% (start, end, file_size))
1009
if cur + self._truncated_ranges >= len(ranges):
1010
# Abruptly ends the response and close the connection
1011
self.close_connection = 1
1013
self.send_range_content(file, start, end - start + 1)
1016
self.wfile.write(boundary_line)
1019
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
1021
_req_handler_class = TruncatedMultipleRangeRequestHandler
1024
super(TestTruncatedMultipleRangeServer, self).setUp()
1025
self.build_tree_contents([('a', '0123456789')],)
1027
def test_readv_with_short_reads(self):
1028
server = self.get_readonly_server()
1029
t = self._transport(server.get_url())
1030
# Force separate ranges for each offset
1031
t._bytes_to_read_before_seek = 0
1032
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1033
self.assertEqual((0, '0'), ireadv.next())
1034
self.assertEqual((2, '2'), ireadv.next())
1035
if not self._testing_pycurl():
1036
# Only one request have been issued so far (except for pycurl that
1037
# try to read the whole response at once)
1038
self.assertEqual(1, server.GET_request_nb)
1039
self.assertEqual((4, '45'), ireadv.next())
1040
self.assertEqual((9, '9'), ireadv.next())
1041
# Both implementations issue 3 requests but:
1042
# - urllib does two multiple (4 ranges, then 2 ranges) then a single
1044
# - pycurl does two multiple (4 ranges, 4 ranges) then a single range
1045
self.assertEqual(3, server.GET_request_nb)
1046
# Finally the client have tried a single range request and stays in
1048
self.assertEqual('single', t._range_hint)
1050
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1051
"""Errors out when range specifiers exceed the limit"""
1053
def get_multiple_ranges(self, file, file_size, ranges):
1054
"""Refuses the multiple ranges request"""
1055
tcs = self.server.test_case_server
1056
if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
1058
# Emulate apache behavior
1059
self.send_error(400, "Bad Request")
1061
return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
1062
self, file, file_size, ranges)
1065
class LimitedRangeHTTPServer(http_server.HttpServer):
1066
"""An HttpServer erroring out on requests with too much range specifiers"""
1068
def __init__(self, request_handler=LimitedRangeRequestHandler,
1069
protocol_version=None,
1071
http_server.HttpServer.__init__(self, request_handler,
1072
protocol_version=protocol_version)
1073
self.range_limit = range_limit
1076
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1077
"""Tests readv requests against a server erroring out on too much ranges."""
1079
# Requests with more range specifiers will error out
1082
730
def create_transport_readonly_server(self):
1083
return LimitedRangeHTTPServer(range_limit=self.range_limit,
1084
protocol_version=self._protocol_version)
731
# Requests with more range specifiers will error out
732
return LimitedRangeHTTPServer(range_limit=self.range_limit)
1086
734
def get_transport(self):
1087
735
return self._transport(self.get_readonly_server().get_url())
1089
737
def setUp(self):
1090
http_utils.TestCaseWithWebserver.setUp(self)
738
TestCaseWithWebserver.setUp(self)
1091
739
# We need to manipulate ranges that correspond to real chunks in the
1092
740
# response, so we build a content appropriately.
1093
filler = ''.join(['abcdefghij' for x in range(102)])
741
filler = ''.join(['abcdefghij' for _ in range(102)])
1094
742
content = ''.join(['%04d' % v + filler for v in range(16)])
1095
743
self.build_tree_contents([('a', content)],)
1608
1297
# Only one 'Authentication Required' error should occur
1609
1298
self.assertEqual(1, self.server.auth_required_errors)
1611
def _check_password_prompt(self, scheme, user, actual_prompt):
1612
expected_prompt = (self._password_prompt_prefix
1613
+ ("%s %s@%s:%d, Realm: '%s' password: "
1615
user, self.server.host, self.server.port,
1616
self.server.auth_realm)))
1617
self.assertEquals(expected_prompt, actual_prompt)
1619
def _expected_username_prompt(self, scheme):
1620
return (self._username_prompt_prefix
1621
+ "%s %s:%d, Realm: '%s' username: " % (scheme.upper(),
1622
self.server.host, self.server.port,
1623
self.server.auth_realm))
1625
def test_no_prompt_for_password_when_using_auth_config(self):
1626
if self._testing_pycurl():
1627
raise tests.TestNotApplicable(
1628
'pycurl does not support authentication.conf'
1629
' since it cannot prompt')
1633
stdin_content = 'bar\n' # Not the right password
1634
self.server.add_user(user, password)
1635
t = self.get_user_transport(user, None)
1636
ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1637
stdout=tests.StringIOWrapper())
1638
# Create a minimal config file with the right password
1639
conf = config.AuthenticationConfig()
1640
conf._get_config().update(
1641
{'httptest': {'scheme': 'http', 'port': self.server.port,
1642
'user': user, 'password': password}})
1644
# Issue a request to the server to connect
1645
self.assertEqual('contents of a\n',t.get('a').read())
1646
# stdin should have been left untouched
1647
self.assertEqual(stdin_content, ui.ui_factory.stdin.readline())
1648
# Only one 'Authentication Required' error should occur
1649
self.assertEqual(1, self.server.auth_required_errors)
1651
def test_user_from_auth_conf(self):
1652
if self._testing_pycurl():
1653
raise tests.TestNotApplicable(
1654
'pycurl does not support authentication.conf')
1657
self.server.add_user(user, password)
1658
# Create a minimal config file with the right password
1659
conf = config.AuthenticationConfig()
1660
conf._get_config().update(
1661
{'httptest': {'scheme': 'http', 'port': self.server.port,
1662
'user': user, 'password': password}})
1664
t = self.get_user_transport(None, None)
1665
# Issue a request to the server to connect
1666
self.assertEqual('contents of a\n', t.get('a').read())
1667
# Only one 'Authentication Required' error should occur
1668
self.assertEqual(1, self.server.auth_required_errors)
1670
def test_changing_nonce(self):
1671
if self._auth_server not in (http_utils.HTTPDigestAuthServer,
1672
http_utils.ProxyDigestAuthServer):
1673
raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
1674
if self._testing_pycurl():
1675
raise tests.KnownFailure(
1676
'pycurl does not handle a nonce change')
1677
self.server.add_user('joe', 'foo')
1678
t = self.get_user_transport('joe', 'foo')
1679
self.assertEqual('contents of a\n', t.get('a').read())
1680
self.assertEqual('contents of b\n', t.get('b').read())
1681
# Only one 'Authentication Required' error should have
1683
self.assertEqual(1, self.server.auth_required_errors)
1684
# The server invalidates the current nonce
1685
self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1686
self.assertEqual('contents of a\n', t.get('a').read())
1687
# Two 'Authentication Required' errors should occur (the
1688
# initial 'who are you' and a second 'who are you' with the new nonce)
1689
self.assertEqual(2, self.server.auth_required_errors)
1301
class TestHTTPAuth(TestAuth):
1302
"""Test HTTP authentication schemes.
1304
Daughter classes MUST inherit from TestCaseWithWebserver too.
1307
_auth_header = 'Authorization'
1310
TestCaseWithWebserver.setUp(self)
1311
self.server = self.get_readonly_server()
1312
TestAuth.setUp(self)
1314
def get_user_transport(self, user=None, password=None):
1315
return self._transport(self.get_user_url(user, password))
1693
1318
class TestProxyAuth(TestAuth):
1694
"""Test proxy authentication schemes."""
1319
"""Test proxy authentication schemes.
1321
Daughter classes MUST also inherit from TestCaseWithWebserver.
1696
1323
_auth_header = 'Proxy-authorization'
1697
_password_prompt_prefix = 'Proxy '
1698
_username_prompt_prefix = 'Proxy '
1700
1325
def setUp(self):
1701
super(TestProxyAuth, self).setUp()
1326
TestCaseWithWebserver.setUp(self)
1327
self.server = self.get_readonly_server()
1702
1328
self._old_env = {}
1703
1329
self.addCleanup(self._restore_env)
1330
TestAuth.setUp(self)
1704
1331
# Override the contents to avoid false positives
1705
1332
self.build_tree_contents([('a', 'not proxied contents of a\n'),
1706
1333
('b', 'not proxied contents of b\n'),
1720
1347
for name, value in self._old_env.iteritems():
1721
1348
osutils.set_or_unset_env(name, value)
1723
def test_empty_pass(self):
1724
if self._testing_pycurl():
1726
if pycurl.version_info()[1] < '7.16.0':
1727
raise tests.KnownFailure(
1728
'pycurl < 7.16.0 does not handle empty proxy passwords')
1729
super(TestProxyAuth, self).test_empty_pass()
1732
class SampleSocket(object):
1733
"""A socket-like object for use in testing the HTTP request handler."""
1735
def __init__(self, socket_read_content):
1736
"""Constructs a sample socket.
1738
:param socket_read_content: a byte sequence
1740
# Use plain python StringIO so we can monkey-patch the close method to
1741
# not discard the contents.
1742
from StringIO import StringIO
1743
self.readfile = StringIO(socket_read_content)
1744
self.writefile = StringIO()
1745
self.writefile.close = lambda: None
1747
def makefile(self, mode='r', bufsize=None):
1749
return self.readfile
1751
return self.writefile
1754
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1757
super(SmartHTTPTunnellingTest, self).setUp()
1758
# We use the VFS layer as part of HTTP tunnelling tests.
1759
self._captureVar('BZR_NO_SMART_VFS', None)
1760
self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1762
def create_transport_readonly_server(self):
1763
return http_utils.HTTPServerWithSmarts(
1764
protocol_version=self._protocol_version)
1766
def test_open_bzrdir(self):
1767
branch = self.make_branch('relpath')
1768
http_server = self.get_readonly_server()
1769
url = http_server.get_url() + 'relpath'
1770
bd = bzrdir.BzrDir.open(url)
1771
self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1773
def test_bulk_data(self):
1774
# We should be able to send and receive bulk data in a single message.
1775
# The 'readv' command in the smart protocol both sends and receives
1776
# bulk data, so we use that.
1777
self.build_tree(['data-file'])
1778
http_server = self.get_readonly_server()
1779
http_transport = self._transport(http_server.get_url())
1780
medium = http_transport.get_smart_medium()
1781
# Since we provide the medium, the url below will be mostly ignored
1782
# during the test, as long as the path is '/'.
1783
remote_transport = remote.RemoteTransport('bzr://fake_host/',
1786
[(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
1788
def test_http_send_smart_request(self):
1790
post_body = 'hello\n'
1791
expected_reply_body = 'ok\x012\n'
1793
http_server = self.get_readonly_server()
1794
http_transport = self._transport(http_server.get_url())
1795
medium = http_transport.get_smart_medium()
1796
response = medium.send_http_smart_request(post_body)
1797
reply_body = response.read()
1798
self.assertEqual(expected_reply_body, reply_body)
1800
def test_smart_http_server_post_request_handler(self):
1801
httpd = self.get_readonly_server()._get_httpd()
1803
socket = SampleSocket(
1804
'POST /.bzr/smart %s \r\n' % self._protocol_version
1805
# HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
1807
+ 'Content-Length: 6\r\n'
1810
# Beware: the ('localhost', 80) below is the
1811
# client_address parameter, but we don't have one because
1812
# we have defined a socket which is not bound to an
1813
# address. The test framework never uses this client
1814
# address, so far...
1815
request_handler = http_utils.SmartRequestHandler(socket,
1818
response = socket.writefile.getvalue()
1819
self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
1820
# This includes the end of the HTTP headers, and all the body.
1821
expected_end_of_response = '\r\n\r\nok\x012\n'
1822
self.assertEndsWith(response, expected_end_of_response)
1825
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
1826
"""No smart server here request handler."""
1829
self.send_error(403, "Forbidden")
1832
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
1833
"""Test smart client behaviour against an http server without smarts."""
1835
_req_handler_class = ForbiddenRequestHandler
1837
def test_probe_smart_server(self):
1838
"""Test error handling against server refusing smart requests."""
1839
server = self.get_readonly_server()
1840
t = self._transport(server.get_url())
1841
# No need to build a valid smart request here, the server will not even
1842
# try to interpret it.
1843
self.assertRaises(errors.SmartProtocolError,
1844
t.get_smart_medium().send_http_smart_request,
1847
class Test_redirected_to(tests.TestCase):
1849
def test_redirected_to_subdir(self):
1850
t = self._transport('http://www.example.com/foo')
1851
r = t._redirected_to('http://www.example.com/foo',
1852
'http://www.example.com/foo/subdir')
1853
self.assertIsInstance(r, type(t))
1854
# Both transports share the some connection
1855
self.assertEquals(t._get_connection(), r._get_connection())
1857
def test_redirected_to_self_with_slash(self):
1858
t = self._transport('http://www.example.com/foo')
1859
r = t._redirected_to('http://www.example.com/foo',
1860
'http://www.example.com/foo/')
1861
self.assertIsInstance(r, type(t))
1862
# Both transports share the some connection (one can argue that we
1863
# should return the exact same transport here, but that seems
1865
self.assertEquals(t._get_connection(), r._get_connection())
1867
def test_redirected_to_host(self):
1868
t = self._transport('http://www.example.com/foo')
1869
r = t._redirected_to('http://www.example.com/foo',
1870
'http://foo.example.com/foo/subdir')
1871
self.assertIsInstance(r, type(t))
1873
def test_redirected_to_same_host_sibling_protocol(self):
1874
t = self._transport('http://www.example.com/foo')
1875
r = t._redirected_to('http://www.example.com/foo',
1876
'https://www.example.com/foo')
1877
self.assertIsInstance(r, type(t))
1879
def test_redirected_to_same_host_different_protocol(self):
1880
t = self._transport('http://www.example.com/foo')
1881
r = t._redirected_to('http://www.example.com/foo',
1882
'ftp://www.example.com/foo')
1883
self.assertNotEquals(type(r), type(t))
1885
def test_redirected_to_different_host_same_user(self):
1886
t = self._transport('http://joe@www.example.com/foo')
1887
r = t._redirected_to('http://www.example.com/foo',
1888
'https://foo.example.com/foo')
1889
self.assertIsInstance(r, type(t))
1890
self.assertEquals(t._user, r._user)
1893
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1894
"""Request handler for a unique and pre-defined request.
1896
The only thing we care about here is how many bytes travel on the wire. But
1897
since we want to measure it for a real http client, we have to send it
1900
We expect to receive a *single* request nothing more (and we won't even
1901
check what request it is, we just measure the bytes read until an empty
1905
def handle_one_request(self):
1906
tcs = self.server.test_case_server
1907
requestline = self.rfile.readline()
1908
headers = self.MessageClass(self.rfile, 0)
1909
# We just read: the request, the headers, an empty line indicating the
1910
# end of the headers.
1911
bytes_read = len(requestline)
1912
for line in headers.headers:
1913
bytes_read += len(line)
1914
bytes_read += len('\r\n')
1915
if requestline.startswith('POST'):
1916
# The body should be a single line (or we don't know where it ends
1917
# and we don't want to issue a blocking read)
1918
body = self.rfile.readline()
1919
bytes_read += len(body)
1920
tcs.bytes_read = bytes_read
1922
# We set the bytes written *before* issuing the write, the client is
1923
# supposed to consume every produced byte *before* checking that value.
1925
# Doing the oppposite may lead to test failure: we may be interrupted
1926
# after the write but before updating the value. The client can then
1927
# continue and read the value *before* we can update it. And yes,
1928
# this has been observed -- vila 20090129
1929
tcs.bytes_written = len(tcs.canned_response)
1930
self.wfile.write(tcs.canned_response)
1933
class ActivityServerMixin(object):
1935
def __init__(self, protocol_version):
1936
super(ActivityServerMixin, self).__init__(
1937
request_handler=PredefinedRequestHandler,
1938
protocol_version=protocol_version)
1939
# Bytes read and written by the server
1941
self.bytes_written = 0
1942
self.canned_response = None
1945
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
1949
if tests.HTTPSServerFeature.available():
1950
from bzrlib.tests import https_server
1951
class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
1955
class TestActivity(tests.TestCase):
1956
"""Test socket activity reporting.
1958
We use a special purpose server to control the bytes sent and received and
1959
be able to predict the activity on the client socket.
1963
tests.TestCase.setUp(self)
1964
self.server = self._activity_server(self._protocol_version)
1966
self.activities = {}
1967
def report_activity(t, bytes, direction):
1968
count = self.activities.get(direction, 0)
1970
self.activities[direction] = count
1972
# We override at class level because constructors may propagate the
1973
# bound method and render instance overriding ineffective (an
1974
# alternative would be to define a specific ui factory instead...)
1975
self.orig_report_activity = self._transport._report_activity
1976
self._transport._report_activity = report_activity
1979
self._transport._report_activity = self.orig_report_activity
1980
self.server.tearDown()
1981
tests.TestCase.tearDown(self)
1983
def get_transport(self):
1984
return self._transport(self.server.get_url())
1986
def assertActivitiesMatch(self):
1987
self.assertEqual(self.server.bytes_read,
1988
self.activities.get('write', 0), 'written bytes')
1989
self.assertEqual(self.server.bytes_written,
1990
self.activities.get('read', 0), 'read bytes')
1993
self.server.canned_response = '''HTTP/1.1 200 OK\r
1994
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
1995
Server: Apache/2.0.54 (Fedora)\r
1996
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
1997
ETag: "56691-23-38e9ae00"\r
1998
Accept-Ranges: bytes\r
1999
Content-Length: 35\r
2001
Content-Type: text/plain; charset=UTF-8\r
2003
Bazaar-NG meta directory, format 1
2005
t = self.get_transport()
2006
self.assertEqual('Bazaar-NG meta directory, format 1\n',
2007
t.get('foo/bar').read())
2008
self.assertActivitiesMatch()
2011
self.server.canned_response = '''HTTP/1.1 200 OK\r
2012
Server: SimpleHTTP/0.6 Python/2.5.2\r
2013
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
2014
Content-type: application/octet-stream\r
2015
Content-Length: 20\r
2016
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
2019
t = self.get_transport()
2020
self.assertTrue(t.has('foo/bar'))
2021
self.assertActivitiesMatch()
2023
def test_readv(self):
2024
self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
2025
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
2026
Server: Apache/2.0.54 (Fedora)\r
2027
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
2028
ETag: "238a3c-16ec2-805c5540"\r
2029
Accept-Ranges: bytes\r
2030
Content-Length: 1534\r
2032
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
2035
--418470f848b63279b\r
2036
Content-type: text/plain; charset=UTF-8\r
2037
Content-range: bytes 0-254/93890\r
2039
mbp@sourcefrog.net-20050309040815-13242001617e4a06
2040
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
2041
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
2042
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
2043
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
2045
--418470f848b63279b\r
2046
Content-type: text/plain; charset=UTF-8\r
2047
Content-range: bytes 1000-2049/93890\r
2050
mbp@sourcefrog.net-20050311063625-07858525021f270b
2051
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
2052
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
2053
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
2054
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
2055
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
2056
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
2057
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
2058
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
2059
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
2060
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
2061
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
2062
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
2063
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
2064
mbp@sourcefrog.net-20050313120651-497bd231b19df600
2065
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
2066
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
2067
mbp@sourcefrog.net-20050314025539-637a636692c055cf
2068
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
2069
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
2071
--418470f848b63279b--\r
2073
t = self.get_transport()
2074
# Remember that the request is ignored and that the ranges below
2075
# doesn't have to match the canned response.
2076
l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
2077
self.assertEqual(2, len(l))
2078
self.assertActivitiesMatch()
2080
def test_post(self):
2081
self.server.canned_response = '''HTTP/1.1 200 OK\r
2082
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2083
Server: Apache/2.0.54 (Fedora)\r
2084
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2085
ETag: "56691-23-38e9ae00"\r
2086
Accept-Ranges: bytes\r
2087
Content-Length: 35\r
2089
Content-Type: text/plain; charset=UTF-8\r
2091
lalala whatever as long as itsssss
2093
t = self.get_transport()
2094
# We must send a single line of body bytes, see
2095
# PredefinedRequestHandler.handle_one_request
2096
code, f = t._post('abc def end-of-body\n')
2097
self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2098
self.assertActivitiesMatch()
1351
class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
1352
"""Test http basic authentication scheme"""
1354
_transport = HttpTransport_urllib
1356
def create_transport_readonly_server(self):
1357
return HTTPBasicAuthServer()
1360
class TestHTTPProxyBasicAuth(TestProxyAuth, TestCaseWithWebserver):
1361
"""Test proxy basic authentication scheme"""
1363
_transport = HttpTransport_urllib
1365
def create_transport_readonly_server(self):
1366
return ProxyBasicAuthServer()
1369
class TestDigestAuth(object):
1370
"""Digest Authentication specific tests"""
1372
def test_changing_nonce(self):
1373
self.server.add_user('joe', 'foo')
1374
t = self.get_user_transport('joe', 'foo')
1375
self.assertEqual('contents of a\n', t.get('a').read())
1376
self.assertEqual('contents of b\n', t.get('b').read())
1377
# Only one 'Authentication Required' error should have
1379
self.assertEqual(1, self.server.auth_required_errors)
1380
# The server invalidates the current nonce
1381
self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1382
self.assertEqual('contents of a\n', t.get('a').read())
1383
# Two 'Authentication Required' errors should occur (the
1384
# initial 'who are you' and a second 'who are you' with the new nonce)
1385
self.assertEqual(2, self.server.auth_required_errors)
1388
class TestHTTPDigestAuth(TestHTTPAuth, TestDigestAuth, TestCaseWithWebserver):
1389
"""Test http digest authentication scheme"""
1391
_transport = HttpTransport_urllib
1393
def create_transport_readonly_server(self):
1394
return HTTPDigestAuthServer()
1397
class TestHTTPProxyDigestAuth(TestProxyAuth, TestDigestAuth,
1398
TestCaseWithWebserver):
1399
"""Test proxy digest authentication scheme"""
1401
_transport = HttpTransport_urllib
1403
def create_transport_readonly_server(self):
1404
return ProxyDigestAuthServer()