112
191
self.received_bytes = ''
195
return '%s://%s:%s/' % (self.scheme, self.host, self.port)
197
def start_server(self):
115
198
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
116
199
self._sock.bind(('127.0.0.1', 0))
117
200
self.host, self.port = self._sock.getsockname()
118
201
self._ready = threading.Event()
119
self._thread = threading.Thread(target=self._accept_read_and_reply)
120
self._thread.setDaemon(True)
202
self._thread = test_server.TestThread(
203
sync_event=self._ready, target=self._accept_read_and_reply)
121
204
self._thread.start()
205
if 'threads' in tests.selftest_debug_flags:
206
sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
124
209
def _accept_read_and_reply(self):
125
210
self._sock.listen(1)
126
211
self._ready.set()
127
self._sock.settimeout(5)
129
conn, address = self._sock.accept()
130
# On win32, the accepted connection will be non-blocking to start
131
# with because we're using settimeout.
132
conn.setblocking(True)
212
conn, address = self._sock.accept()
213
if self._expect_body_tail is not None:
133
214
while not self.received_bytes.endswith(self._expect_body_tail):
134
215
self.received_bytes += conn.recv(4096)
135
216
conn.sendall('HTTP/1.1 200 OK\r\n')
136
except socket.timeout:
137
# Make sure the client isn't stuck waiting for us to e.g. accept.
138
218
self._sock.close()
139
219
except socket.error:
140
220
# The client may have already closed the socket.
223
def stop_server(self):
225
# Issue a fake connection to wake up the server and allow it to
227
fake_conn = osutils.connect_socket((self.host, self.port))
146
229
except socket.error:
147
230
# We might have already closed it. We don't care.
235
if 'threads' in tests.selftest_debug_flags:
236
sys.stderr.write('Thread joined: %s\n' % (self._thread.ident,))
239
class TestAuthHeader(tests.TestCase):
241
def parse_header(self, header, auth_handler_class=None):
242
if auth_handler_class is None:
243
auth_handler_class = _urllib2_wrappers.AbstractAuthHandler
244
self.auth_handler = auth_handler_class()
245
return self.auth_handler._parse_auth_header(header)
247
def test_empty_header(self):
248
scheme, remainder = self.parse_header('')
249
self.assertEqual('', scheme)
250
self.assertIs(None, remainder)
252
def test_negotiate_header(self):
253
scheme, remainder = self.parse_header('Negotiate')
254
self.assertEqual('negotiate', scheme)
255
self.assertIs(None, remainder)
257
def test_basic_header(self):
258
scheme, remainder = self.parse_header(
259
'Basic realm="Thou should not pass"')
260
self.assertEqual('basic', scheme)
261
self.assertEqual('realm="Thou should not pass"', remainder)
263
def test_build_basic_header_with_long_creds(self):
264
handler = _urllib2_wrappers.BasicAuthHandler()
265
user = 'user' * 10 # length 40
266
password = 'password' * 5 # length 40
267
header = handler.build_auth_header(
268
dict(user=user, password=password), None)
269
# https://bugs.launchpad.net/bzr/+bug/1606203 was caused by incorrectly
270
# creating a header value with an embedded '\n'
271
self.assertFalse('\n' in header)
273
def test_basic_extract_realm(self):
274
scheme, remainder = self.parse_header(
275
'Basic realm="Thou should not pass"',
276
_urllib2_wrappers.BasicAuthHandler)
277
match, realm = self.auth_handler.extract_realm(remainder)
278
self.assertTrue(match is not None)
279
self.assertEqual('Thou should not pass', realm)
281
def test_digest_header(self):
282
scheme, remainder = self.parse_header(
283
'Digest realm="Thou should not pass"')
284
self.assertEqual('digest', scheme)
285
self.assertEqual('realm="Thou should not pass"', remainder)
288
class TestHTTPRangeParsing(tests.TestCase):
291
super(TestHTTPRangeParsing, self).setUp()
292
# We focus on range parsing here and ignore everything else
293
class RequestHandler(http_server.TestingHTTPRequestHandler):
294
def setup(self): pass
295
def handle(self): pass
296
def finish(self): pass
298
self.req_handler = RequestHandler(None, None, None)
300
def assertRanges(self, ranges, header, file_size):
301
self.assertEqual(ranges,
302
self.req_handler._parse_ranges(header, file_size))
304
def test_simple_range(self):
305
self.assertRanges([(0,2)], 'bytes=0-2', 12)
308
self.assertRanges([(8, 11)], 'bytes=-4', 12)
310
def test_tail_bigger_than_file(self):
311
self.assertRanges([(0, 11)], 'bytes=-99', 12)
313
def test_range_without_end(self):
314
self.assertRanges([(4, 11)], 'bytes=4-', 12)
316
def test_invalid_ranges(self):
317
self.assertRanges(None, 'bytes=12-22', 12)
318
self.assertRanges(None, 'bytes=1-3,12-22', 12)
319
self.assertRanges(None, 'bytes=-', 12)
322
class TestHTTPServer(tests.TestCase):
323
"""Test the HTTP servers implementations."""
325
def test_invalid_protocol(self):
326
class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
328
protocol_version = 'HTTP/0.1'
330
self.assertRaises(httplib.UnknownProtocol,
331
http_server.HttpServer, BogusRequestHandler)
333
def test_force_invalid_protocol(self):
334
self.assertRaises(httplib.UnknownProtocol,
335
http_server.HttpServer, protocol_version='HTTP/0.1')
337
def test_server_start_and_stop(self):
338
server = http_server.HttpServer()
339
self.addCleanup(server.stop_server)
340
server.start_server()
341
self.assertTrue(server.server is not None)
342
self.assertTrue(server.server.serving is not None)
343
self.assertTrue(server.server.serving)
345
def test_create_http_server_one_zero(self):
346
class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
348
protocol_version = 'HTTP/1.0'
350
server = http_server.HttpServer(RequestHandlerOneZero)
351
self.start_server(server)
352
self.assertIsInstance(server.server, http_server.TestingHTTPServer)
354
def test_create_http_server_one_one(self):
355
class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
357
protocol_version = 'HTTP/1.1'
359
server = http_server.HttpServer(RequestHandlerOneOne)
360
self.start_server(server)
361
self.assertIsInstance(server.server,
362
http_server.TestingThreadingHTTPServer)
364
def test_create_http_server_force_one_one(self):
365
class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
367
protocol_version = 'HTTP/1.0'
369
server = http_server.HttpServer(RequestHandlerOneZero,
370
protocol_version='HTTP/1.1')
371
self.start_server(server)
372
self.assertIsInstance(server.server,
373
http_server.TestingThreadingHTTPServer)
375
def test_create_http_server_force_one_zero(self):
376
class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
378
protocol_version = 'HTTP/1.1'
380
server = http_server.HttpServer(RequestHandlerOneOne,
381
protocol_version='HTTP/1.0')
382
self.start_server(server)
383
self.assertIsInstance(server.server,
384
http_server.TestingHTTPServer)
153
387
class TestWithTransport_pycurl(object):
154
388
"""Test case to inherit from if pycurl is present"""
156
390
def _get_pycurl_maybe(self):
158
from bzrlib.transport.http._pycurl import PyCurlTransport
159
return PyCurlTransport
160
except errors.DependencyNotPresent:
161
raise TestSkipped('pycurl not present')
391
self.requireFeature(features.pycurl)
392
return PyCurlTransport
163
394
_transport = property(_get_pycurl_maybe)
166
class TestHttpUrls(TestCase):
168
# TODO: This should be moved to authorization tests once they
171
def test_url_parsing(self):
173
url = extract_auth('http://example.com', f)
174
self.assertEquals('http://example.com', url)
175
self.assertEquals(0, len(f.credentials))
176
url = extract_auth('http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
177
self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
178
self.assertEquals(1, len(f.credentials))
179
self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
183
class TestHttpTransportUrls(object):
184
"""Test the http urls.
186
This MUST be used by daughter classes that also inherit from
189
We can't inherit directly from TestCase or the
190
test framework will try to create an instance which cannot
191
run, its implementation being incomplete.
397
class TestHttpTransportUrls(tests.TestCase):
398
"""Test the http urls."""
400
scenarios = vary_by_http_client_implementation()
194
402
def test_abs_url(self):
195
403
"""Construction of absolute http URLs"""
196
t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
404
t = self._transport('http://example.com/bzr/bzr.dev/')
197
405
eq = self.assertEqualDiff
198
eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
199
eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
200
eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
406
eq(t.abspath('.'), 'http://example.com/bzr/bzr.dev')
407
eq(t.abspath('foo/bar'), 'http://example.com/bzr/bzr.dev/foo/bar')
408
eq(t.abspath('.bzr'), 'http://example.com/bzr/bzr.dev/.bzr')
201
409
eq(t.abspath('.bzr/1//2/./3'),
202
'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
410
'http://example.com/bzr/bzr.dev/.bzr/1/2/3')
204
412
def test_invalid_http_urls(self):
205
413
"""Trap invalid construction of urls"""
206
t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
207
self.assertRaises((errors.InvalidURL, errors.ConnectionError),
414
self._transport('http://example.com/bzr/bzr.dev/')
415
self.assertRaises(errors.InvalidURL,
209
'http://http://bazaar-vcs.org/bzr/bzr.dev/')
417
'http://http://example.com/bzr/bzr.dev/')
211
419
def test_http_root_urls(self):
212
420
"""Construction of URLs from server root"""
213
t = self._transport('http://bzr.ozlabs.org/')
421
t = self._transport('http://example.com/')
214
422
eq = self.assertEqualDiff
215
423
eq(t.abspath('.bzr/tree-version'),
216
'http://bzr.ozlabs.org/.bzr/tree-version')
424
'http://example.com/.bzr/tree-version')
218
426
def test_http_impl_urls(self):
219
427
"""There are servers which ask for particular clients to connect"""
220
428
server = self._server()
429
server.start_server()
223
431
url = server.get_url()
224
self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
432
self.assertTrue(url.startswith('%s://' % self._url_protocol))
229
class TestHttpUrls_urllib(TestHttpTransportUrls, TestCase):
230
"""Test http urls with urllib"""
232
_transport = HttpTransport_urllib
233
_server = HttpServer_urllib
234
_qualified_prefix = 'http+urllib'
237
class TestHttpUrls_pycurl(TestWithTransport_pycurl, TestHttpTransportUrls,
239
"""Test http urls with pycurl"""
241
_server = HttpServer_PyCurl
242
_qualified_prefix = 'http+pycurl'
437
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
244
439
# TODO: This should really be moved into another pycurl
245
440
# specific test. When https tests will be implemented, take
653
840
self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
654
841
t.readv, 'a', [(12,2)])
843
def test_readv_multiple_get_requests(self):
844
server = self.get_readonly_server()
845
t = self.get_readonly_transport()
846
# force transport to issue multiple requests
847
t._max_readv_combine = 1
848
t._max_get_ranges = 1
849
l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
850
self.assertEqual(l[0], (0, '0'))
851
self.assertEqual(l[1], (1, '1'))
852
self.assertEqual(l[2], (3, '34'))
853
self.assertEqual(l[3], (9, '9'))
854
# The server should have issued 4 requests
855
self.assertEqual(4, server.GET_request_nb)
857
def test_readv_get_max_size(self):
858
server = self.get_readonly_server()
859
t = self.get_readonly_transport()
860
# force transport to issue multiple requests by limiting the number of
861
# bytes by request. Note that this apply to coalesced offsets only, a
862
# single range will keep its size even if bigger than the limit.
864
l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
865
self.assertEqual(l[0], (0, '0'))
866
self.assertEqual(l[1], (1, '1'))
867
self.assertEqual(l[2], (2, '2345'))
868
self.assertEqual(l[3], (6, '6789'))
869
# The server should have issued 3 requests
870
self.assertEqual(3, server.GET_request_nb)
872
def test_complete_readv_leave_pipe_clean(self):
873
server = self.get_readonly_server()
874
t = self.get_readonly_transport()
875
# force transport to issue multiple requests
877
list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
878
# The server should have issued 3 requests
879
self.assertEqual(3, server.GET_request_nb)
880
self.assertEqual('0123456789', t.get_bytes('a'))
881
self.assertEqual(4, server.GET_request_nb)
883
def test_incomplete_readv_leave_pipe_clean(self):
884
server = self.get_readonly_server()
885
t = self.get_readonly_transport()
886
# force transport to issue multiple requests
888
# Don't collapse readv results into a list so that we leave unread
889
# bytes on the socket
890
ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
891
self.assertEqual((0, '0'), ireadv.next())
892
# The server should have issued one request so far
893
self.assertEqual(1, server.GET_request_nb)
894
self.assertEqual('0123456789', t.get_bytes('a'))
895
# get_bytes issued an additional request, the readv pending ones are
897
self.assertEqual(2, server.GET_request_nb)
900
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
901
"""Always reply to range request as if they were single.
903
Don't be explicit about it, just to annoy the clients.
906
def get_multiple_ranges(self, file, file_size, ranges):
907
"""Answer as if it was a single range request and ignores the rest"""
908
(start, end) = ranges[0]
909
return self.get_single_range(file, file_size, start, end)
657
912
class TestSingleRangeRequestServer(TestRangeRequestServer):
658
913
"""Test readv against a server which accept only single range requests"""
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"""
915
_req_handler_class = SingleRangeRequestHandler
918
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
919
"""Only reply to simple range requests, errors out on multiple"""
921
def get_multiple_ranges(self, file, file_size, ranges):
922
"""Refuses the multiple ranges request"""
925
self.send_error(416, "Requested range not satisfiable")
927
(start, end) = ranges[0]
928
return self.get_single_range(file, file_size, start, end)
677
931
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
678
932
"""Test readv against a server which only accept single range requests"""
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"""
934
_req_handler_class = SingleOnlyRangeRequestHandler
937
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
938
"""Ignore range requests without notice"""
941
# Update the statistics
942
self.server.test_case_server.GET_request_nb += 1
943
# Just bypass the range handling done by TestingHTTPRequestHandler
944
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
697
947
class TestNoRangeRequestServer(TestRangeRequestServer):
698
948
"""Test readv against a server which do not accept range requests"""
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.
950
_req_handler_class = NoRangeRequestHandler
953
class MultipleRangeWithoutContentLengthRequestHandler(
954
http_server.TestingHTTPRequestHandler):
955
"""Reply to multiple range requests without content length header."""
957
def get_multiple_ranges(self, file, file_size, ranges):
958
self.send_response(206)
959
self.send_header('Accept-Ranges', 'bytes')
960
# XXX: this is strange; the 'random' name below seems undefined and
961
# yet the tests pass -- mbp 2010-10-11 bug 658773
962
boundary = "%d" % random.randint(0,0x7FFFFFFF)
963
self.send_header("Content-Type",
964
"multipart/byteranges; boundary=%s" % boundary)
966
for (start, end) in ranges:
967
self.wfile.write("--%s\r\n" % boundary)
968
self.send_header("Content-type", 'application/octet-stream')
969
self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
973
self.send_range_content(file, start, end - start + 1)
975
self.wfile.write("--%s\r\n" % boundary)
978
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
980
_req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
983
class TruncatedMultipleRangeRequestHandler(
984
http_server.TestingHTTPRequestHandler):
985
"""Reply to multiple range requests truncating the last ones.
987
This server generates responses whose Content-Length describes all the
988
ranges, but fail to include the last ones leading to client short reads.
989
This has been observed randomly with lighttpd (bug #179368).
992
_truncated_ranges = 2
994
def get_multiple_ranges(self, file, file_size, ranges):
995
self.send_response(206)
996
self.send_header('Accept-Ranges', 'bytes')
998
self.send_header('Content-Type',
999
'multipart/byteranges; boundary=%s' % boundary)
1000
boundary_line = '--%s\r\n' % boundary
1001
# Calculate the Content-Length
1003
for (start, end) in ranges:
1004
content_length += len(boundary_line)
1005
content_length += self._header_line_length(
1006
'Content-type', 'application/octet-stream')
1007
content_length += self._header_line_length(
1008
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
1009
content_length += len('\r\n') # end headers
1010
content_length += end - start # + 1
1011
content_length += len(boundary_line)
1012
self.send_header('Content-length', content_length)
1015
# Send the multipart body
1017
for (start, end) in ranges:
1018
self.wfile.write(boundary_line)
1019
self.send_header('Content-type', 'application/octet-stream')
1020
self.send_header('Content-Range', 'bytes %d-%d/%d'
1021
% (start, end, file_size))
1023
if cur + self._truncated_ranges >= len(ranges):
1024
# Abruptly ends the response and close the connection
1025
self.close_connection = 1
1027
self.send_range_content(file, start, end - start + 1)
1030
self.wfile.write(boundary_line)
1033
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
1035
_req_handler_class = TruncatedMultipleRangeRequestHandler
1038
super(TestTruncatedMultipleRangeServer, self).setUp()
1039
self.build_tree_contents([('a', '0123456789')],)
1041
def test_readv_with_short_reads(self):
1042
server = self.get_readonly_server()
1043
t = self.get_readonly_transport()
1044
# Force separate ranges for each offset
1045
t._bytes_to_read_before_seek = 0
1046
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1047
self.assertEqual((0, '0'), ireadv.next())
1048
self.assertEqual((2, '2'), ireadv.next())
1049
if not self._testing_pycurl():
1050
# Only one request have been issued so far (except for pycurl that
1051
# try to read the whole response at once)
1052
self.assertEqual(1, server.GET_request_nb)
1053
self.assertEqual((4, '45'), ireadv.next())
1054
self.assertEqual((9, '9'), ireadv.next())
1055
# Both implementations issue 3 requests but:
1056
# - urllib does two multiple (4 ranges, then 2 ranges) then a single
1058
# - pycurl does two multiple (4 ranges, 4 ranges) then a single range
1059
self.assertEqual(3, server.GET_request_nb)
1060
# Finally the client have tried a single range request and stays in
1062
self.assertEqual('single', t._range_hint)
1065
class TruncatedBeforeBoundaryRequestHandler(
1066
http_server.TestingHTTPRequestHandler):
1067
"""Truncation before a boundary, like in bug 198646"""
1069
_truncated_ranges = 1
1071
def get_multiple_ranges(self, file, file_size, ranges):
1072
self.send_response(206)
1073
self.send_header('Accept-Ranges', 'bytes')
1075
self.send_header('Content-Type',
1076
'multipart/byteranges; boundary=%s' % boundary)
1077
boundary_line = '--%s\r\n' % boundary
1078
# Calculate the Content-Length
1080
for (start, end) in ranges:
1081
content_length += len(boundary_line)
1082
content_length += self._header_line_length(
1083
'Content-type', 'application/octet-stream')
1084
content_length += self._header_line_length(
1085
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
1086
content_length += len('\r\n') # end headers
1087
content_length += end - start # + 1
1088
content_length += len(boundary_line)
1089
self.send_header('Content-length', content_length)
1092
# Send the multipart body
1094
for (start, end) in ranges:
1095
if cur + self._truncated_ranges >= len(ranges):
1096
# Abruptly ends the response and close the connection
1097
self.close_connection = 1
1099
self.wfile.write(boundary_line)
1100
self.send_header('Content-type', 'application/octet-stream')
1101
self.send_header('Content-Range', 'bytes %d-%d/%d'
1102
% (start, end, file_size))
1104
self.send_range_content(file, start, end - start + 1)
1107
self.wfile.write(boundary_line)
1110
class TestTruncatedBeforeBoundary(TestSpecificRequestHandler):
1111
"""Tests the case of bug 198646, disconnecting before a boundary."""
1113
_req_handler_class = TruncatedBeforeBoundaryRequestHandler
1116
super(TestTruncatedBeforeBoundary, self).setUp()
1117
self.build_tree_contents([('a', '0123456789')],)
1119
def test_readv_with_short_reads(self):
1120
server = self.get_readonly_server()
1121
t = self.get_readonly_transport()
1122
# Force separate ranges for each offset
1123
t._bytes_to_read_before_seek = 0
1124
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1125
self.assertEqual((0, '0'), ireadv.next())
1126
self.assertEqual((2, '2'), ireadv.next())
1127
self.assertEqual((4, '45'), ireadv.next())
1128
self.assertEqual((9, '9'), ireadv.next())
1131
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1132
"""Errors out when range specifiers exceed the limit"""
1134
def get_multiple_ranges(self, file, file_size, ranges):
1135
"""Refuses the multiple ranges request"""
1136
tcs = self.server.test_case_server
1137
if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
1139
# Emulate apache behavior
1140
self.send_error(400, "Bad Request")
1142
return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
1143
self, file, file_size, ranges)
1146
class LimitedRangeHTTPServer(http_server.HttpServer):
1147
"""An HttpServer erroring out on requests with too much range specifiers"""
1149
def __init__(self, request_handler=LimitedRangeRequestHandler,
1150
protocol_version=None,
1152
http_server.HttpServer.__init__(self, request_handler,
1153
protocol_version=protocol_version)
1154
self.range_limit = range_limit
1157
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1158
"""Tests readv requests against a server erroring out on too much ranges."""
1160
scenarios = multiply_scenarios(
1161
vary_by_http_client_implementation(),
1162
vary_by_http_protocol_version(),
1165
# Requests with more range specifiers will error out
730
1168
def create_transport_readonly_server(self):
731
# Requests with more range specifiers will error out
732
return LimitedRangeHTTPServer(range_limit=self.range_limit)
734
def get_transport(self):
735
return self._transport(self.get_readonly_server().get_url())
1169
return LimitedRangeHTTPServer(range_limit=self.range_limit,
1170
protocol_version=self._protocol_version)
737
1172
def setUp(self):
738
TestCaseWithWebserver.setUp(self)
1173
super(TestLimitedRangeRequestServer, self).setUp()
739
1174
# We need to manipulate ranges that correspond to real chunks in the
740
1175
# response, so we build a content appropriately.
741
filler = ''.join(['abcdefghij' for _ in range(102)])
1176
filler = ''.join(['abcdefghij' for x in range(102)])
742
1177
content = ''.join(['%04d' % v + filler for v in range(16)])
743
1178
self.build_tree_contents([('a', content)],)
745
1180
def test_few_ranges(self):
746
t = self.get_transport()
1181
t = self.get_readonly_transport()
747
1182
l = list(t.readv('a', ((0, 4), (1024, 4), )))
748
1183
self.assertEqual(l[0], (0, '0000'))
749
1184
self.assertEqual(l[1], (1024, '0001'))
750
1185
self.assertEqual(1, self.get_readonly_server().GET_request_nb)
752
def test_a_lot_of_ranges(self):
753
t = self.get_transport()
1187
def test_more_ranges(self):
1188
t = self.get_readonly_transport()
754
1189
l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
755
1190
self.assertEqual(l[0], (0, '0000'))
756
1191
self.assertEqual(l[1], (1024, '0001'))
757
1192
self.assertEqual(l[2], (4096, '0004'))
758
1193
self.assertEqual(l[3], (8192, '0008'))
759
1194
# The server will refuse to serve the first request (too much ranges),
760
# a second request will succeeds.
1195
# a second request will succeed.
761
1196
self.assertEqual(2, self.get_readonly_server().GET_request_nb)
764
class TestLimitedRangeRequestServer_urllib(TestLimitedRangeRequestServer,
765
TestCaseWithWebserver):
766
"""Tests limited range requests server for urllib implementation"""
768
_transport = HttpTransport_urllib
771
class TestLimitedRangeRequestServer_pycurl(TestWithTransport_pycurl,
772
TestLimitedRangeRequestServer,
773
TestCaseWithWebserver):
774
"""Tests limited range requests server for pycurl implementation"""
778
class TestHttpProxyWhiteBox(TestCase):
1199
class TestHttpProxyWhiteBox(tests.TestCase):
779
1200
"""Whitebox test proxy http authorization.
781
1202
Only the urllib implementation is tested here.
791
def _install_env(self, env):
792
for name, value in env.iteritems():
793
self._old_env[name] = osutils.set_or_unset_env(name, value)
795
def _restore_env(self):
796
for name, value in self._old_env.iteritems():
797
osutils.set_or_unset_env(name, value)
799
1205
def _proxied_request(self):
800
handler = ProxyHandler(PasswordManager())
801
request = Request('GET','http://baz/buzzle')
1206
handler = _urllib2_wrappers.ProxyHandler()
1207
request = _urllib2_wrappers.Request('GET', 'http://baz/buzzle')
802
1208
handler.set_proxy(request, 'http')
1211
def assertEvaluateProxyBypass(self, expected, host, no_proxy):
1212
handler = _urllib2_wrappers.ProxyHandler()
1213
self.assertEqual(expected,
1214
handler.evaluate_proxy_bypass(host, no_proxy))
805
1216
def test_empty_user(self):
806
self._install_env({'http_proxy': 'http://bar.com'})
1217
self.overrideEnv('http_proxy', 'http://bar.com')
1218
request = self._proxied_request()
1219
self.assertFalse(request.headers.has_key('Proxy-authorization'))
1221
def test_user_with_at(self):
1222
self.overrideEnv('http_proxy',
1223
'http://username@domain:password@proxy_host:1234')
807
1224
request = self._proxied_request()
808
1225
self.assertFalse(request.headers.has_key('Proxy-authorization'))
810
1227
def test_invalid_proxy(self):
811
1228
"""A proxy env variable without scheme"""
812
self._install_env({'http_proxy': 'host:1234'})
1229
self.overrideEnv('http_proxy', 'host:1234')
813
1230
self.assertRaises(errors.InvalidURL, self._proxied_request)
816
class TestProxyHttpServer(object):
1232
def test_evaluate_proxy_bypass_true(self):
1233
"""The host is not proxied"""
1234
self.assertEvaluateProxyBypass(True, 'example.com', 'example.com')
1235
self.assertEvaluateProxyBypass(True, 'bzr.example.com', '*example.com')
1237
def test_evaluate_proxy_bypass_false(self):
1238
"""The host is proxied"""
1239
self.assertEvaluateProxyBypass(False, 'bzr.example.com', None)
1241
def test_evaluate_proxy_bypass_unknown(self):
1242
"""The host is not explicitly proxied"""
1243
self.assertEvaluateProxyBypass(None, 'example.com', 'not.example.com')
1244
self.assertEvaluateProxyBypass(None, 'bzr.example.com', 'example.com')
1246
def test_evaluate_proxy_bypass_empty_entries(self):
1247
"""Ignore empty entries"""
1248
self.assertEvaluateProxyBypass(None, 'example.com', '')
1249
self.assertEvaluateProxyBypass(None, 'example.com', ',')
1250
self.assertEvaluateProxyBypass(None, 'example.com', 'foo,,bar')
1253
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
817
1254
"""Tests proxy server.
819
This MUST be used by daughter classes that also inherit from
820
TestCaseWithTwoWebservers.
822
We can't inherit directly from TestCaseWithTwoWebservers or
823
the test framework will try to create an instance which
824
cannot run, its implementation being incomplete.
826
1256
Be aware that we do not setup a real proxy here. Instead, we
827
1257
check that the *connection* goes through the proxy by serving
828
1258
different content (the faked proxy server append '-proxied'
829
1259
to the file names).
1262
scenarios = multiply_scenarios(
1263
vary_by_http_client_implementation(),
1264
vary_by_http_protocol_version(),
832
1267
# FIXME: We don't have an https server available, so we don't
833
# test https connections.
835
# FIXME: Once the test suite is better fitted to test
836
# authorization schemes, test proxy authorizations too (see
1268
# test https connections. --vila toolongago
839
1270
def setUp(self):
840
TestCaseWithTwoWebservers.setUp(self)
1271
super(TestProxyHttpServer, self).setUp()
1272
self.transport_secondary_server = http_utils.ProxyServer
841
1273
self.build_tree_contents([('foo', 'contents of foo\n'),
842
1274
('foo-proxied', 'proxied contents of foo\n')])
843
1275
# Let's setup some attributes for tests
844
self.server = self.get_readonly_server()
845
self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
846
self.no_proxy_host = self.proxy_address
1276
server = self.get_readonly_server()
1277
self.server_host_port = '%s:%d' % (server.host, server.port)
1278
if self._testing_pycurl():
1279
# Oh my ! pycurl does not check for the port as part of
1280
# no_proxy :-( So we just test the host part
1281
self.no_proxy_host = server.host
1283
self.no_proxy_host = self.server_host_port
847
1284
# The secondary server is the proxy
848
self.proxy = self.get_secondary_server()
849
self.proxy_url = self.proxy.get_url()
852
def create_transport_secondary_server(self):
853
"""Creates an http server that will serve files with
854
'-proxied' appended to their names.
858
def _install_env(self, env):
859
for name, value in env.iteritems():
860
self._old_env[name] = osutils.set_or_unset_env(name, value)
862
def _restore_env(self):
863
for name, value in self._old_env.iteritems():
864
osutils.set_or_unset_env(name, value)
866
def proxied_in_env(self, env):
867
self._install_env(env)
868
url = self.server.get_url()
869
t = self._transport(url)
871
self.assertEqual(t.get('foo').read(), 'proxied contents of foo\n')
875
def not_proxied_in_env(self, env):
876
self._install_env(env)
877
url = self.server.get_url()
878
t = self._transport(url)
880
self.assertEqual(t.get('foo').read(), 'contents of foo\n')
1285
self.proxy_url = self.get_secondary_url()
1286
if self._testing_pycurl():
1287
self.proxy_url = self.proxy_url.replace('+pycurl', '')
1289
def _testing_pycurl(self):
1290
# TODO: This is duplicated for lots of the classes in this file
1291
return (features.pycurl.available()
1292
and self._transport == PyCurlTransport)
1294
def assertProxied(self):
1295
t = self.get_readonly_transport()
1296
self.assertEqual('proxied contents of foo\n', t.get('foo').read())
1298
def assertNotProxied(self):
1299
t = self.get_readonly_transport()
1300
self.assertEqual('contents of foo\n', t.get('foo').read())
884
1302
def test_http_proxy(self):
885
self.proxied_in_env({'http_proxy': self.proxy_url})
1303
self.overrideEnv('http_proxy', self.proxy_url)
1304
self.assertProxied()
887
1306
def test_HTTP_PROXY(self):
888
self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
1307
if self._testing_pycurl():
1308
# pycurl does not check HTTP_PROXY for security reasons
1309
# (for use in a CGI context that we do not care
1310
# about. Should we ?)
1311
raise tests.TestNotApplicable(
1312
'pycurl does not check HTTP_PROXY for security reasons')
1313
self.overrideEnv('HTTP_PROXY', self.proxy_url)
1314
self.assertProxied()
890
1316
def test_all_proxy(self):
891
self.proxied_in_env({'all_proxy': self.proxy_url})
1317
self.overrideEnv('all_proxy', self.proxy_url)
1318
self.assertProxied()
893
1320
def test_ALL_PROXY(self):
894
self.proxied_in_env({'ALL_PROXY': self.proxy_url})
1321
self.overrideEnv('ALL_PROXY', self.proxy_url)
1322
self.assertProxied()
896
1324
def test_http_proxy_with_no_proxy(self):
897
self.not_proxied_in_env({'http_proxy': self.proxy_url,
898
'no_proxy': self.no_proxy_host})
1325
self.overrideEnv('no_proxy', self.no_proxy_host)
1326
self.overrideEnv('http_proxy', self.proxy_url)
1327
self.assertNotProxied()
900
1329
def test_HTTP_PROXY_with_NO_PROXY(self):
901
self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
902
'NO_PROXY': self.no_proxy_host})
1330
if self._testing_pycurl():
1331
raise tests.TestNotApplicable(
1332
'pycurl does not check HTTP_PROXY for security reasons')
1333
self.overrideEnv('NO_PROXY', self.no_proxy_host)
1334
self.overrideEnv('HTTP_PROXY', self.proxy_url)
1335
self.assertNotProxied()
904
1337
def test_all_proxy_with_no_proxy(self):
905
self.not_proxied_in_env({'all_proxy': self.proxy_url,
906
'no_proxy': self.no_proxy_host})
1338
self.overrideEnv('no_proxy', self.no_proxy_host)
1339
self.overrideEnv('all_proxy', self.proxy_url)
1340
self.assertNotProxied()
908
1342
def test_ALL_PROXY_with_NO_PROXY(self):
909
self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
910
'NO_PROXY': self.no_proxy_host})
912
def test_http_proxy_without_scheme(self):
913
self.assertRaises(errors.InvalidURL,
915
{'http_proxy': self.proxy_address})
918
class TestProxyHttpServer_urllib(TestProxyHttpServer,
919
TestCaseWithTwoWebservers):
920
"""Tests proxy server for urllib implementation"""
922
_transport = HttpTransport_urllib
925
class TestProxyHttpServer_pycurl(TestWithTransport_pycurl,
927
TestCaseWithTwoWebservers):
928
"""Tests proxy server for pycurl implementation"""
931
TestProxyHttpServer.setUp(self)
932
# Oh my ! pycurl does not check for the port as part of
933
# no_proxy :-( So we just test the host part
934
self.no_proxy_host = 'localhost'
936
def test_HTTP_PROXY(self):
937
# pycurl do not check HTTP_PROXY for security reasons
938
# (for use in a CGI context that we do not care
939
# about. Should we ?)
942
def test_HTTP_PROXY_with_NO_PROXY(self):
945
def test_http_proxy_without_scheme(self):
946
# pycurl *ignores* invalid proxy env variables. If that
947
# ever change in the future, this test will fail
948
# indicating that pycurl do not ignore anymore such
950
self.not_proxied_in_env({'http_proxy': self.proxy_address})
953
class TestRanges(object):
954
"""Test the Range header in GET methods..
956
This MUST be used by daughter classes that also inherit from
957
TestCaseWithWebserver.
959
We can't inherit directly from TestCaseWithWebserver or the
960
test framework will try to create an instance which cannot
961
run, its implementation being incomplete.
965
TestCaseWithWebserver.setUp(self)
1343
self.overrideEnv('NO_PROXY', self.no_proxy_host)
1344
self.overrideEnv('ALL_PROXY', self.proxy_url)
1345
self.assertNotProxied()
1347
def test_http_proxy_without_scheme(self):
1348
self.overrideEnv('http_proxy', self.server_host_port)
1349
if self._testing_pycurl():
1350
# pycurl *ignores* invalid proxy env variables. If that ever change
1351
# in the future, this test will fail indicating that pycurl do not
1352
# ignore anymore such variables.
1353
self.assertNotProxied()
1355
self.assertRaises(errors.InvalidURL, self.assertProxied)
1358
class TestRanges(http_utils.TestCaseWithWebserver):
1359
"""Test the Range header in GET methods."""
1361
scenarios = multiply_scenarios(
1362
vary_by_http_client_implementation(),
1363
vary_by_http_protocol_version(),
1367
super(TestRanges, self).setUp()
966
1368
self.build_tree_contents([('a', '0123456789')],)
967
server = self.get_readonly_server()
968
self.transport = self._transport(server.get_url())
1370
def create_transport_readonly_server(self):
1371
return http_server.HttpServer(protocol_version=self._protocol_version)
970
1373
def _file_contents(self, relpath, ranges):
1374
t = self.get_readonly_transport()
971
1375
offsets = [ (start, end - start + 1) for start, end in ranges]
972
coalesce = self.transport._coalesce_offsets
1376
coalesce = t._coalesce_offsets
973
1377
coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
974
code, data = self.transport._get(relpath, coalesced)
1378
code, data = t._get(relpath, coalesced)
975
1379
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
976
1380
for start, end in ranges:
977
1381
data.seek(start)
978
1382
yield data.read(end - start + 1)
980
1384
def _file_tail(self, relpath, tail_amount):
981
code, data = self.transport._get(relpath, [], tail_amount)
1385
t = self.get_readonly_transport()
1386
code, data = t._get(relpath, [], tail_amount)
982
1387
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
983
data.seek(-tail_amount + 1, 2)
1388
data.seek(-tail_amount, 2)
984
1389
return data.read(tail_amount)
986
1391
def test_range_header(self):
988
1393
map(self.assertEqual,['0', '234'],
989
1394
list(self._file_contents('a', [(0,0), (2,4)])),)
1396
def test_range_header_tail(self):
991
1397
self.assertEqual('789', self._file_tail('a', 3))
992
# Syntactically invalid range
993
self.assertListRaises(errors.InvalidRange,
1399
def test_syntactically_invalid_range_header(self):
1400
self.assertListRaises(errors.InvalidHttpRange,
994
1401
self._file_contents, 'a', [(4, 3)])
995
# Semantically invalid range
996
self.assertListRaises(errors.InvalidRange,
1403
def test_semantically_invalid_range_header(self):
1404
self.assertListRaises(errors.InvalidHttpRange,
997
1405
self._file_contents, 'a', [(42, 128)])
1000
class TestRanges_urllib(TestRanges, TestCaseWithWebserver):
1001
"""Test the Range header in GET methods for urllib implementation"""
1003
_transport = HttpTransport_urllib
1006
class TestRanges_pycurl(TestWithTransport_pycurl,
1008
TestCaseWithWebserver):
1009
"""Test the Range header in GET methods for pycurl implementation"""
1012
class TestHTTPRedirections(object):
1013
"""Test redirection between http servers.
1015
This MUST be used by daughter classes that also inherit from
1016
TestCaseWithRedirectedWebserver.
1018
We can't inherit directly from TestCaseWithTwoWebservers or the
1019
test framework will try to create an instance which cannot
1020
run, its implementation being incomplete.
1023
def create_transport_secondary_server(self):
1024
"""Create the secondary server redirecting to the primary server"""
1025
new = self.get_readonly_server()
1027
redirecting = HTTPServerRedirecting()
1028
redirecting.redirect_to(new.host, new.port)
1408
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1409
"""Test redirection between http servers."""
1411
scenarios = multiply_scenarios(
1412
vary_by_http_client_implementation(),
1413
vary_by_http_protocol_version(),
1031
1416
def setUp(self):
1032
1417
super(TestHTTPRedirections, self).setUp()
1335
1864
('b-proxied', 'contents of b\n'),
1338
def get_user_transport(self, user=None, password=None):
1339
self._install_env({'all_proxy': self.get_user_url(user, password)})
1340
return self._transport(self.server.get_url())
1342
def _install_env(self, env):
1343
for name, value in env.iteritems():
1344
self._old_env[name] = osutils.set_or_unset_env(name, value)
1346
def _restore_env(self):
1347
for name, value in self._old_env.iteritems():
1348
osutils.set_or_unset_env(name, value)
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()
1867
def get_user_transport(self, user, password):
1868
proxy_url = self.get_user_url(user, password)
1869
if self._testing_pycurl():
1870
proxy_url = proxy_url.replace('+pycurl', '')
1871
self.overrideEnv('all_proxy', proxy_url)
1872
return TestAuth.get_user_transport(self, user, password)
1874
def test_empty_pass(self):
1875
if self._testing_pycurl():
1877
if pycurl.version_info()[1] < '7.16.0':
1879
'pycurl < 7.16.0 does not handle empty proxy passwords')
1880
super(TestProxyAuth, self).test_empty_pass()
1883
class SampleSocket(object):
1884
"""A socket-like object for use in testing the HTTP request handler."""
1886
def __init__(self, socket_read_content):
1887
"""Constructs a sample socket.
1889
:param socket_read_content: a byte sequence
1891
# Use plain python StringIO so we can monkey-patch the close method to
1892
# not discard the contents.
1893
from StringIO import StringIO
1894
self.readfile = StringIO(socket_read_content)
1895
self.writefile = StringIO()
1896
self.writefile.close = lambda: None
1897
self.close = lambda: None
1899
def makefile(self, mode='r', bufsize=None):
1901
return self.readfile
1903
return self.writefile
1906
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1908
scenarios = multiply_scenarios(
1909
vary_by_http_client_implementation(),
1910
vary_by_http_protocol_version(),
1914
super(SmartHTTPTunnellingTest, self).setUp()
1915
# We use the VFS layer as part of HTTP tunnelling tests.
1916
self.overrideEnv('BZR_NO_SMART_VFS', None)
1917
self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1918
self.http_server = self.get_readonly_server()
1920
def create_transport_readonly_server(self):
1921
server = http_utils.HTTPServerWithSmarts(
1922
protocol_version=self._protocol_version)
1923
server._url_protocol = self._url_protocol
1926
def test_open_controldir(self):
1927
branch = self.make_branch('relpath')
1928
url = self.http_server.get_url() + 'relpath'
1929
bd = controldir.ControlDir.open(url)
1930
self.addCleanup(bd.transport.disconnect)
1931
self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1933
def test_bulk_data(self):
1934
# We should be able to send and receive bulk data in a single message.
1935
# The 'readv' command in the smart protocol both sends and receives
1936
# bulk data, so we use that.
1937
self.build_tree(['data-file'])
1938
http_transport = transport.get_transport_from_url(
1939
self.http_server.get_url())
1940
medium = http_transport.get_smart_medium()
1941
# Since we provide the medium, the url below will be mostly ignored
1942
# during the test, as long as the path is '/'.
1943
remote_transport = remote.RemoteTransport('bzr://fake_host/',
1946
[(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
1948
def test_http_send_smart_request(self):
1950
post_body = 'hello\n'
1951
expected_reply_body = 'ok\x012\n'
1953
http_transport = transport.get_transport_from_url(
1954
self.http_server.get_url())
1955
medium = http_transport.get_smart_medium()
1956
response = medium.send_http_smart_request(post_body)
1957
reply_body = response.read()
1958
self.assertEqual(expected_reply_body, reply_body)
1960
def test_smart_http_server_post_request_handler(self):
1961
httpd = self.http_server.server
1963
socket = SampleSocket(
1964
'POST /.bzr/smart %s \r\n' % self._protocol_version
1965
# HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
1967
+ 'Content-Length: 6\r\n'
1970
# Beware: the ('localhost', 80) below is the
1971
# client_address parameter, but we don't have one because
1972
# we have defined a socket which is not bound to an
1973
# address. The test framework never uses this client
1974
# address, so far...
1975
request_handler = http_utils.SmartRequestHandler(socket,
1978
response = socket.writefile.getvalue()
1979
self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
1980
# This includes the end of the HTTP headers, and all the body.
1981
expected_end_of_response = '\r\n\r\nok\x012\n'
1982
self.assertEndsWith(response, expected_end_of_response)
1985
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
1986
"""No smart server here request handler."""
1989
self.send_error(403, "Forbidden")
1992
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
1993
"""Test smart client behaviour against an http server without smarts."""
1995
_req_handler_class = ForbiddenRequestHandler
1997
def test_probe_smart_server(self):
1998
"""Test error handling against server refusing smart requests."""
1999
t = self.get_readonly_transport()
2000
# No need to build a valid smart request here, the server will not even
2001
# try to interpret it.
2002
self.assertRaises(errors.SmartProtocolError,
2003
t.get_smart_medium().send_http_smart_request,
2007
class Test_redirected_to(tests.TestCase):
2009
scenarios = vary_by_http_client_implementation()
2011
def test_redirected_to_subdir(self):
2012
t = self._transport('http://www.example.com/foo')
2013
r = t._redirected_to('http://www.example.com/foo',
2014
'http://www.example.com/foo/subdir')
2015
self.assertIsInstance(r, type(t))
2016
# Both transports share the some connection
2017
self.assertEqual(t._get_connection(), r._get_connection())
2018
self.assertEqual('http://www.example.com/foo/subdir/', r.base)
2020
def test_redirected_to_self_with_slash(self):
2021
t = self._transport('http://www.example.com/foo')
2022
r = t._redirected_to('http://www.example.com/foo',
2023
'http://www.example.com/foo/')
2024
self.assertIsInstance(r, type(t))
2025
# Both transports share the some connection (one can argue that we
2026
# should return the exact same transport here, but that seems
2028
self.assertEqual(t._get_connection(), r._get_connection())
2030
def test_redirected_to_host(self):
2031
t = self._transport('http://www.example.com/foo')
2032
r = t._redirected_to('http://www.example.com/foo',
2033
'http://foo.example.com/foo/subdir')
2034
self.assertIsInstance(r, type(t))
2035
self.assertEqual('http://foo.example.com/foo/subdir/',
2038
def test_redirected_to_same_host_sibling_protocol(self):
2039
t = self._transport('http://www.example.com/foo')
2040
r = t._redirected_to('http://www.example.com/foo',
2041
'https://www.example.com/foo')
2042
self.assertIsInstance(r, type(t))
2043
self.assertEqual('https://www.example.com/foo/',
2046
def test_redirected_to_same_host_different_protocol(self):
2047
t = self._transport('http://www.example.com/foo')
2048
r = t._redirected_to('http://www.example.com/foo',
2049
'ftp://www.example.com/foo')
2050
self.assertNotEqual(type(r), type(t))
2051
self.assertEqual('ftp://www.example.com/foo/', r.external_url())
2053
def test_redirected_to_same_host_specific_implementation(self):
2054
t = self._transport('http://www.example.com/foo')
2055
r = t._redirected_to('http://www.example.com/foo',
2056
'https+urllib://www.example.com/foo')
2057
self.assertEqual('https://www.example.com/foo/', r.external_url())
2059
def test_redirected_to_different_host_same_user(self):
2060
t = self._transport('http://joe@www.example.com/foo')
2061
r = t._redirected_to('http://www.example.com/foo',
2062
'https://foo.example.com/foo')
2063
self.assertIsInstance(r, type(t))
2064
self.assertEqual(t._parsed_url.user, r._parsed_url.user)
2065
self.assertEqual('https://joe@foo.example.com/foo/', r.external_url())
2068
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
2069
"""Request handler for a unique and pre-defined request.
2071
The only thing we care about here is how many bytes travel on the wire. But
2072
since we want to measure it for a real http client, we have to send it
2075
We expect to receive a *single* request nothing more (and we won't even
2076
check what request it is, we just measure the bytes read until an empty
2080
def _handle_one_request(self):
2081
tcs = self.server.test_case_server
2082
requestline = self.rfile.readline()
2083
headers = self.MessageClass(self.rfile, 0)
2084
# We just read: the request, the headers, an empty line indicating the
2085
# end of the headers.
2086
bytes_read = len(requestline)
2087
for line in headers.headers:
2088
bytes_read += len(line)
2089
bytes_read += len('\r\n')
2090
if requestline.startswith('POST'):
2091
# The body should be a single line (or we don't know where it ends
2092
# and we don't want to issue a blocking read)
2093
body = self.rfile.readline()
2094
bytes_read += len(body)
2095
tcs.bytes_read = bytes_read
2097
# We set the bytes written *before* issuing the write, the client is
2098
# supposed to consume every produced byte *before* checking that value.
2100
# Doing the oppposite may lead to test failure: we may be interrupted
2101
# after the write but before updating the value. The client can then
2102
# continue and read the value *before* we can update it. And yes,
2103
# this has been observed -- vila 20090129
2104
tcs.bytes_written = len(tcs.canned_response)
2105
self.wfile.write(tcs.canned_response)
2108
class ActivityServerMixin(object):
2110
def __init__(self, protocol_version):
2111
super(ActivityServerMixin, self).__init__(
2112
request_handler=PredefinedRequestHandler,
2113
protocol_version=protocol_version)
2114
# Bytes read and written by the server
2116
self.bytes_written = 0
2117
self.canned_response = None
2120
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
2124
if features.HTTPSServerFeature.available():
2125
from bzrlib.tests import https_server
2126
class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
2130
class TestActivityMixin(object):
2131
"""Test socket activity reporting.
2133
We use a special purpose server to control the bytes sent and received and
2134
be able to predict the activity on the client socket.
2138
self.server = self._activity_server(self._protocol_version)
2139
self.server.start_server()
2140
self.addCleanup(self.server.stop_server)
2141
_activities = {} # Don't close over self and create a cycle
2142
def report_activity(t, bytes, direction):
2143
count = _activities.get(direction, 0)
2145
_activities[direction] = count
2146
self.activities = _activities
2147
# We override at class level because constructors may propagate the
2148
# bound method and render instance overriding ineffective (an
2149
# alternative would be to define a specific ui factory instead...)
2150
self.overrideAttr(self._transport, '_report_activity', report_activity)
2152
def get_transport(self):
2153
t = self._transport(self.server.get_url())
2154
# FIXME: Needs cleanup -- vila 20100611
2157
def assertActivitiesMatch(self):
2158
self.assertEqual(self.server.bytes_read,
2159
self.activities.get('write', 0), 'written bytes')
2160
self.assertEqual(self.server.bytes_written,
2161
self.activities.get('read', 0), 'read bytes')
2164
self.server.canned_response = '''HTTP/1.1 200 OK\r
2165
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2166
Server: Apache/2.0.54 (Fedora)\r
2167
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2168
ETag: "56691-23-38e9ae00"\r
2169
Accept-Ranges: bytes\r
2170
Content-Length: 35\r
2172
Content-Type: text/plain; charset=UTF-8\r
2174
Bazaar-NG meta directory, format 1
2176
t = self.get_transport()
2177
self.assertEqual('Bazaar-NG meta directory, format 1\n',
2178
t.get('foo/bar').read())
2179
self.assertActivitiesMatch()
2182
self.server.canned_response = '''HTTP/1.1 200 OK\r
2183
Server: SimpleHTTP/0.6 Python/2.5.2\r
2184
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
2185
Content-type: application/octet-stream\r
2186
Content-Length: 20\r
2187
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
2190
t = self.get_transport()
2191
self.assertTrue(t.has('foo/bar'))
2192
self.assertActivitiesMatch()
2194
def test_readv(self):
2195
self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
2196
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
2197
Server: Apache/2.0.54 (Fedora)\r
2198
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
2199
ETag: "238a3c-16ec2-805c5540"\r
2200
Accept-Ranges: bytes\r
2201
Content-Length: 1534\r
2203
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
2206
--418470f848b63279b\r
2207
Content-type: text/plain; charset=UTF-8\r
2208
Content-range: bytes 0-254/93890\r
2210
mbp@sourcefrog.net-20050309040815-13242001617e4a06
2211
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
2212
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
2213
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
2214
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
2216
--418470f848b63279b\r
2217
Content-type: text/plain; charset=UTF-8\r
2218
Content-range: bytes 1000-2049/93890\r
2221
mbp@sourcefrog.net-20050311063625-07858525021f270b
2222
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
2223
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
2224
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
2225
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
2226
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
2227
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
2228
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
2229
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
2230
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
2231
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
2232
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
2233
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
2234
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
2235
mbp@sourcefrog.net-20050313120651-497bd231b19df600
2236
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
2237
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
2238
mbp@sourcefrog.net-20050314025539-637a636692c055cf
2239
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
2240
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
2242
--418470f848b63279b--\r
2244
t = self.get_transport()
2245
# Remember that the request is ignored and that the ranges below
2246
# doesn't have to match the canned response.
2247
l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
2248
self.assertEqual(2, len(l))
2249
self.assertActivitiesMatch()
2251
def test_post(self):
2252
self.server.canned_response = '''HTTP/1.1 200 OK\r
2253
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2254
Server: Apache/2.0.54 (Fedora)\r
2255
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2256
ETag: "56691-23-38e9ae00"\r
2257
Accept-Ranges: bytes\r
2258
Content-Length: 35\r
2260
Content-Type: text/plain; charset=UTF-8\r
2262
lalala whatever as long as itsssss
2264
t = self.get_transport()
2265
# We must send a single line of body bytes, see
2266
# PredefinedRequestHandler._handle_one_request
2267
code, f = t._post('abc def end-of-body\n')
2268
self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2269
self.assertActivitiesMatch()
2272
class TestActivity(tests.TestCase, TestActivityMixin):
2274
scenarios = multiply_scenarios(
2275
vary_by_http_activity(),
2276
vary_by_http_protocol_version(),
2280
super(TestActivity, self).setUp()
2281
TestActivityMixin.setUp(self)
2284
class TestNoReportActivity(tests.TestCase, TestActivityMixin):
2286
# Unlike TestActivity, we are really testing ReportingFileSocket and
2287
# ReportingSocket, so we don't need all the parametrization. Since
2288
# ReportingFileSocket and ReportingSocket are wrappers, it's easier to
2289
# test them through their use by the transport than directly (that's a
2290
# bit less clean but far more simpler and effective).
2291
_activity_server = ActivityHTTPServer
2292
_protocol_version = 'HTTP/1.1'
2295
super(TestNoReportActivity, self).setUp()
2296
self._transport =_urllib.HttpTransport_urllib
2297
TestActivityMixin.setUp(self)
2299
def assertActivitiesMatch(self):
2300
# Nothing to check here
2304
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
2305
"""Test authentication on the redirected http server."""
2307
scenarios = vary_by_http_protocol_version()
2309
_auth_header = 'Authorization'
2310
_password_prompt_prefix = ''
2311
_username_prompt_prefix = ''
2312
_auth_server = http_utils.HTTPBasicAuthServer
2313
_transport = _urllib.HttpTransport_urllib
2316
super(TestAuthOnRedirected, self).setUp()
2317
self.build_tree_contents([('a','a'),
2319
('1/a', 'redirected once'),
2321
new_prefix = 'http://%s:%s' % (self.new_server.host,
2322
self.new_server.port)
2323
self.old_server.redirections = [
2324
('(.*)', r'%s/1\1' % (new_prefix), 301),]
2325
self.old_transport = self.get_old_transport()
2326
self.new_server.add_user('joe', 'foo')
2327
cleanup_http_redirection_connections(self)
2329
def create_transport_readonly_server(self):
2330
server = self._auth_server(protocol_version=self._protocol_version)
2331
server._url_protocol = self._url_protocol
2337
def test_auth_on_redirected_via_do_catching_redirections(self):
2338
self.redirections = 0
2340
def redirected(t, exception, redirection_notice):
2341
self.redirections += 1
2342
redirected_t = t._redirected_to(exception.source, exception.target)
2343
self.addCleanup(redirected_t.disconnect)
2346
stdout = tests.StringIOWrapper()
2347
stderr = tests.StringIOWrapper()
2348
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
2349
stdout=stdout, stderr=stderr)
2350
self.assertEqual('redirected once',
2351
transport.do_catching_redirections(
2352
self.get_a, self.old_transport, redirected).read())
2353
self.assertEqual(1, self.redirections)
2354
# stdin should be empty
2355
self.assertEqual('', ui.ui_factory.stdin.readline())
2356
# stdout should be empty, stderr will contains the prompts
2357
self.assertEqual('', stdout.getvalue())
2359
def test_auth_on_redirected_via_following_redirections(self):
2360
self.new_server.add_user('joe', 'foo')
2361
stdout = tests.StringIOWrapper()
2362
stderr = tests.StringIOWrapper()
2363
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
2364
stdout=stdout, stderr=stderr)
2365
t = self.old_transport
2366
req = RedirectedRequest('GET', t.abspath('a'))
2367
new_prefix = 'http://%s:%s' % (self.new_server.host,
2368
self.new_server.port)
2369
self.old_server.redirections = [
2370
('(.*)', r'%s/1\1' % (new_prefix), 301),]
2371
self.assertEqual('redirected once', t._perform(req).read())
2372
# stdin should be empty
2373
self.assertEqual('', ui.ui_factory.stdin.readline())
2374
# stdout should be empty, stderr will contains the prompts
2375
self.assertEqual('', stdout.getvalue())