185
112
self.received_bytes = ''
189
return '%s://%s:%s/' % (self.scheme, self.host, self.port)
191
def start_server(self):
192
115
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
193
116
self._sock.bind(('127.0.0.1', 0))
194
117
self.host, self.port = self._sock.getsockname()
195
118
self._ready = threading.Event()
196
self._thread = test_server.TestThread(
197
sync_event=self._ready, target=self._accept_read_and_reply)
119
self._thread = threading.Thread(target=self._accept_read_and_reply)
120
self._thread.setDaemon(True)
198
121
self._thread.start()
199
if 'threads' in tests.selftest_debug_flags:
200
sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
203
124
def _accept_read_and_reply(self):
204
125
self._sock.listen(1)
205
126
self._ready.set()
206
conn, address = self._sock.accept()
207
if self._expect_body_tail is not None:
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)
208
133
while not self.received_bytes.endswith(self._expect_body_tail):
209
134
self.received_bytes += conn.recv(4096)
210
135
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.
212
138
self._sock.close()
213
139
except socket.error:
214
140
# The client may have already closed the socket.
217
def stop_server(self):
219
# Issue a fake connection to wake up the server and allow it to
221
fake_conn = osutils.connect_socket((self.host, self.port))
223
146
except socket.error:
224
147
# We might have already closed it. We don't care.
229
if 'threads' in tests.selftest_debug_flags:
230
sys.stderr.write('Thread joined: %s\n' % (self._thread.ident,))
233
class TestAuthHeader(tests.TestCase):
235
def parse_header(self, header, auth_handler_class=None):
236
if auth_handler_class is None:
237
auth_handler_class = _urllib2_wrappers.AbstractAuthHandler
238
self.auth_handler = auth_handler_class()
239
return self.auth_handler._parse_auth_header(header)
241
def test_empty_header(self):
242
scheme, remainder = self.parse_header('')
243
self.assertEqual('', scheme)
244
self.assertIs(None, remainder)
246
def test_negotiate_header(self):
247
scheme, remainder = self.parse_header('Negotiate')
248
self.assertEqual('negotiate', scheme)
249
self.assertIs(None, remainder)
251
def test_basic_header(self):
252
scheme, remainder = self.parse_header(
253
'Basic realm="Thou should not pass"')
254
self.assertEqual('basic', scheme)
255
self.assertEqual('realm="Thou should not pass"', remainder)
257
def test_basic_extract_realm(self):
258
scheme, remainder = self.parse_header(
259
'Basic realm="Thou should not pass"',
260
_urllib2_wrappers.BasicAuthHandler)
261
match, realm = self.auth_handler.extract_realm(remainder)
262
self.assertTrue(match is not None)
263
self.assertEqual('Thou should not pass', realm)
265
def test_digest_header(self):
266
scheme, remainder = self.parse_header(
267
'Digest realm="Thou should not pass"')
268
self.assertEqual('digest', scheme)
269
self.assertEqual('realm="Thou should not pass"', remainder)
272
class TestHTTPRangeParsing(tests.TestCase):
275
super(TestHTTPRangeParsing, self).setUp()
276
# We focus on range parsing here and ignore everything else
277
class RequestHandler(http_server.TestingHTTPRequestHandler):
278
def setup(self): pass
279
def handle(self): pass
280
def finish(self): pass
282
self.req_handler = RequestHandler(None, None, None)
284
def assertRanges(self, ranges, header, file_size):
285
self.assertEquals(ranges,
286
self.req_handler._parse_ranges(header, file_size))
288
def test_simple_range(self):
289
self.assertRanges([(0,2)], 'bytes=0-2', 12)
292
self.assertRanges([(8, 11)], 'bytes=-4', 12)
294
def test_tail_bigger_than_file(self):
295
self.assertRanges([(0, 11)], 'bytes=-99', 12)
297
def test_range_without_end(self):
298
self.assertRanges([(4, 11)], 'bytes=4-', 12)
300
def test_invalid_ranges(self):
301
self.assertRanges(None, 'bytes=12-22', 12)
302
self.assertRanges(None, 'bytes=1-3,12-22', 12)
303
self.assertRanges(None, 'bytes=-', 12)
306
class TestHTTPServer(tests.TestCase):
307
"""Test the HTTP servers implementations."""
309
def test_invalid_protocol(self):
310
class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
312
protocol_version = 'HTTP/0.1'
314
self.assertRaises(httplib.UnknownProtocol,
315
http_server.HttpServer, BogusRequestHandler)
317
def test_force_invalid_protocol(self):
318
self.assertRaises(httplib.UnknownProtocol,
319
http_server.HttpServer, protocol_version='HTTP/0.1')
321
def test_server_start_and_stop(self):
322
server = http_server.HttpServer()
323
self.addCleanup(server.stop_server)
324
server.start_server()
325
self.assertTrue(server.server is not None)
326
self.assertTrue(server.server.serving is not None)
327
self.assertTrue(server.server.serving)
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.server, 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.server,
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.server,
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.server,
368
http_server.TestingHTTPServer)
371
153
class TestWithTransport_pycurl(object):
372
154
"""Test case to inherit from if pycurl is present"""
374
156
def _get_pycurl_maybe(self):
375
self.requireFeature(features.pycurl)
376
return PyCurlTransport
158
from bzrlib.transport.http._pycurl import PyCurlTransport
159
return PyCurlTransport
160
except errors.DependencyNotPresent:
161
raise TestSkipped('pycurl not present')
378
163
_transport = property(_get_pycurl_maybe)
381
class TestHttpUrls(tests.TestCase):
166
class TestHttpUrls(TestCase):
383
168
# TODO: This should be moved to authorization tests once they
386
171
def test_url_parsing(self):
387
172
f = FakeManager()
388
url = http.extract_auth('http://example.com', f)
389
self.assertEqual('http://example.com', url)
390
self.assertEqual(0, len(f.credentials))
391
url = http.extract_auth(
392
'http://user:pass@example.com/bzr/bzr.dev', f)
393
self.assertEqual('http://example.com/bzr/bzr.dev', url)
394
self.assertEqual(1, len(f.credentials))
395
self.assertEqual([None, 'example.com', 'user', 'pass'],
399
class TestHttpTransportUrls(tests.TestCase):
400
"""Test the http urls."""
402
scenarios = vary_by_http_client_implementation()
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.
404
194
def test_abs_url(self):
405
195
"""Construction of absolute http URLs"""
406
t = self._transport('http://example.com/bzr/bzr.dev/')
196
t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
407
197
eq = self.assertEqualDiff
408
eq(t.abspath('.'), 'http://example.com/bzr/bzr.dev')
409
eq(t.abspath('foo/bar'), 'http://example.com/bzr/bzr.dev/foo/bar')
410
eq(t.abspath('.bzr'), 'http://example.com/bzr/bzr.dev/.bzr')
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')
411
201
eq(t.abspath('.bzr/1//2/./3'),
412
'http://example.com/bzr/bzr.dev/.bzr/1/2/3')
202
'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
414
204
def test_invalid_http_urls(self):
415
205
"""Trap invalid construction of urls"""
416
self._transport('http://example.com/bzr/bzr.dev/')
417
self.assertRaises(errors.InvalidURL,
206
t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
207
self.assertRaises((errors.InvalidURL, errors.ConnectionError),
419
'http://http://example.com/bzr/bzr.dev/')
209
'http://http://bazaar-vcs.org/bzr/bzr.dev/')
421
211
def test_http_root_urls(self):
422
212
"""Construction of URLs from server root"""
423
t = self._transport('http://example.com/')
213
t = self._transport('http://bzr.ozlabs.org/')
424
214
eq = self.assertEqualDiff
425
215
eq(t.abspath('.bzr/tree-version'),
426
'http://example.com/.bzr/tree-version')
216
'http://bzr.ozlabs.org/.bzr/tree-version')
428
218
def test_http_impl_urls(self):
429
219
"""There are servers which ask for particular clients to connect"""
430
220
server = self._server()
431
server.start_server()
433
223
url = server.get_url()
434
self.assertTrue(url.startswith('%s://' % self._url_protocol))
224
self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
439
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
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'
441
244
# TODO: This should really be moved into another pycurl
442
245
# specific test. When https tests will be implemented, take
827
653
self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
828
654
t.readv, 'a', [(12,2)])
830
def test_readv_multiple_get_requests(self):
831
server = self.get_readonly_server()
832
t = self.get_readonly_transport()
833
# force transport to issue multiple requests
834
t._max_readv_combine = 1
835
t._max_get_ranges = 1
836
l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
837
self.assertEqual(l[0], (0, '0'))
838
self.assertEqual(l[1], (1, '1'))
839
self.assertEqual(l[2], (3, '34'))
840
self.assertEqual(l[3], (9, '9'))
841
# The server should have issued 4 requests
842
self.assertEqual(4, server.GET_request_nb)
844
def test_readv_get_max_size(self):
845
server = self.get_readonly_server()
846
t = self.get_readonly_transport()
847
# force transport to issue multiple requests by limiting the number of
848
# bytes by request. Note that this apply to coalesced offsets only, a
849
# single range will keep its size even if bigger than the limit.
851
l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
852
self.assertEqual(l[0], (0, '0'))
853
self.assertEqual(l[1], (1, '1'))
854
self.assertEqual(l[2], (2, '2345'))
855
self.assertEqual(l[3], (6, '6789'))
856
# The server should have issued 3 requests
857
self.assertEqual(3, server.GET_request_nb)
859
def test_complete_readv_leave_pipe_clean(self):
860
server = self.get_readonly_server()
861
t = self.get_readonly_transport()
862
# force transport to issue multiple requests
864
list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
865
# The server should have issued 3 requests
866
self.assertEqual(3, server.GET_request_nb)
867
self.assertEqual('0123456789', t.get_bytes('a'))
868
self.assertEqual(4, server.GET_request_nb)
870
def test_incomplete_readv_leave_pipe_clean(self):
871
server = self.get_readonly_server()
872
t = self.get_readonly_transport()
873
# force transport to issue multiple requests
875
# Don't collapse readv results into a list so that we leave unread
876
# bytes on the socket
877
ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
878
self.assertEqual((0, '0'), ireadv.next())
879
# The server should have issued one request so far
880
self.assertEqual(1, server.GET_request_nb)
881
self.assertEqual('0123456789', t.get_bytes('a'))
882
# get_bytes issued an additional request, the readv pending ones are
884
self.assertEqual(2, server.GET_request_nb)
887
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
888
"""Always reply to range request as if they were single.
890
Don't be explicit about it, just to annoy the clients.
893
def get_multiple_ranges(self, file, file_size, ranges):
894
"""Answer as if it was a single range request and ignores the rest"""
895
(start, end) = ranges[0]
896
return self.get_single_range(file, file_size, start, end)
899
657
class TestSingleRangeRequestServer(TestRangeRequestServer):
900
658
"""Test readv against a server which accept only single range requests"""
902
_req_handler_class = SingleRangeRequestHandler
905
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
906
"""Only reply to simple range requests, errors out on multiple"""
908
def get_multiple_ranges(self, file, file_size, ranges):
909
"""Refuses the multiple ranges request"""
912
self.send_error(416, "Requested range not satisfiable")
914
(start, end) = ranges[0]
915
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"""
918
677
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
919
678
"""Test readv against a server which only accept single range requests"""
921
_req_handler_class = SingleOnlyRangeRequestHandler
924
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
925
"""Ignore range requests without notice"""
928
# Update the statistics
929
self.server.test_case_server.GET_request_nb += 1
930
# Just bypass the range handling done by TestingHTTPRequestHandler
931
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"""
934
697
class TestNoRangeRequestServer(TestRangeRequestServer):
935
698
"""Test readv against a server which do not accept range requests"""
937
_req_handler_class = NoRangeRequestHandler
940
class MultipleRangeWithoutContentLengthRequestHandler(
941
http_server.TestingHTTPRequestHandler):
942
"""Reply to multiple range requests without content length header."""
944
def get_multiple_ranges(self, file, file_size, ranges):
945
self.send_response(206)
946
self.send_header('Accept-Ranges', 'bytes')
947
# XXX: this is strange; the 'random' name below seems undefined and
948
# yet the tests pass -- mbp 2010-10-11 bug 658773
949
boundary = "%d" % random.randint(0,0x7FFFFFFF)
950
self.send_header("Content-Type",
951
"multipart/byteranges; boundary=%s" % boundary)
953
for (start, end) in ranges:
954
self.wfile.write("--%s\r\n" % boundary)
955
self.send_header("Content-type", 'application/octet-stream')
956
self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
960
self.send_range_content(file, start, end - start + 1)
962
self.wfile.write("--%s\r\n" % boundary)
965
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
967
_req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
970
class TruncatedMultipleRangeRequestHandler(
971
http_server.TestingHTTPRequestHandler):
972
"""Reply to multiple range requests truncating the last ones.
974
This server generates responses whose Content-Length describes all the
975
ranges, but fail to include the last ones leading to client short reads.
976
This has been observed randomly with lighttpd (bug #179368).
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.
979
_truncated_ranges = 2
981
def get_multiple_ranges(self, file, file_size, ranges):
982
self.send_response(206)
983
self.send_header('Accept-Ranges', 'bytes')
985
self.send_header('Content-Type',
986
'multipart/byteranges; boundary=%s' % boundary)
987
boundary_line = '--%s\r\n' % boundary
988
# Calculate the Content-Length
990
for (start, end) in ranges:
991
content_length += len(boundary_line)
992
content_length += self._header_line_length(
993
'Content-type', 'application/octet-stream')
994
content_length += self._header_line_length(
995
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
996
content_length += len('\r\n') # end headers
997
content_length += end - start # + 1
998
content_length += len(boundary_line)
999
self.send_header('Content-length', content_length)
1002
# Send the multipart body
1004
for (start, end) in ranges:
1005
self.wfile.write(boundary_line)
1006
self.send_header('Content-type', 'application/octet-stream')
1007
self.send_header('Content-Range', 'bytes %d-%d/%d'
1008
% (start, end, file_size))
1010
if cur + self._truncated_ranges >= len(ranges):
1011
# Abruptly ends the response and close the connection
1012
self.close_connection = 1
1014
self.send_range_content(file, start, end - start + 1)
1017
self.wfile.write(boundary_line)
1020
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
1022
_req_handler_class = TruncatedMultipleRangeRequestHandler
1025
super(TestTruncatedMultipleRangeServer, self).setUp()
1026
self.build_tree_contents([('a', '0123456789')],)
1028
def test_readv_with_short_reads(self):
1029
server = self.get_readonly_server()
1030
t = self.get_readonly_transport()
1031
# Force separate ranges for each offset
1032
t._bytes_to_read_before_seek = 0
1033
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1034
self.assertEqual((0, '0'), ireadv.next())
1035
self.assertEqual((2, '2'), ireadv.next())
1036
if not self._testing_pycurl():
1037
# Only one request have been issued so far (except for pycurl that
1038
# try to read the whole response at once)
1039
self.assertEqual(1, server.GET_request_nb)
1040
self.assertEqual((4, '45'), ireadv.next())
1041
self.assertEqual((9, '9'), ireadv.next())
1042
# Both implementations issue 3 requests but:
1043
# - urllib does two multiple (4 ranges, then 2 ranges) then a single
1045
# - pycurl does two multiple (4 ranges, 4 ranges) then a single range
1046
self.assertEqual(3, server.GET_request_nb)
1047
# Finally the client have tried a single range request and stays in
1049
self.assertEqual('single', t._range_hint)
1052
class TruncatedBeforeBoundaryRequestHandler(
1053
http_server.TestingHTTPRequestHandler):
1054
"""Truncation before a boundary, like in bug 198646"""
1056
_truncated_ranges = 1
1058
def get_multiple_ranges(self, file, file_size, ranges):
1059
self.send_response(206)
1060
self.send_header('Accept-Ranges', 'bytes')
1062
self.send_header('Content-Type',
1063
'multipart/byteranges; boundary=%s' % boundary)
1064
boundary_line = '--%s\r\n' % boundary
1065
# Calculate the Content-Length
1067
for (start, end) in ranges:
1068
content_length += len(boundary_line)
1069
content_length += self._header_line_length(
1070
'Content-type', 'application/octet-stream')
1071
content_length += self._header_line_length(
1072
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
1073
content_length += len('\r\n') # end headers
1074
content_length += end - start # + 1
1075
content_length += len(boundary_line)
1076
self.send_header('Content-length', content_length)
1079
# Send the multipart body
1081
for (start, end) in ranges:
1082
if cur + self._truncated_ranges >= len(ranges):
1083
# Abruptly ends the response and close the connection
1084
self.close_connection = 1
1086
self.wfile.write(boundary_line)
1087
self.send_header('Content-type', 'application/octet-stream')
1088
self.send_header('Content-Range', 'bytes %d-%d/%d'
1089
% (start, end, file_size))
1091
self.send_range_content(file, start, end - start + 1)
1094
self.wfile.write(boundary_line)
1097
class TestTruncatedBeforeBoundary(TestSpecificRequestHandler):
1098
"""Tests the case of bug 198646, disconnecting before a boundary."""
1100
_req_handler_class = TruncatedBeforeBoundaryRequestHandler
1103
super(TestTruncatedBeforeBoundary, self).setUp()
1104
self.build_tree_contents([('a', '0123456789')],)
1106
def test_readv_with_short_reads(self):
1107
server = self.get_readonly_server()
1108
t = self.get_readonly_transport()
1109
# Force separate ranges for each offset
1110
t._bytes_to_read_before_seek = 0
1111
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1112
self.assertEqual((0, '0'), ireadv.next())
1113
self.assertEqual((2, '2'), ireadv.next())
1114
self.assertEqual((4, '45'), ireadv.next())
1115
self.assertEqual((9, '9'), ireadv.next())
1118
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1119
"""Errors out when range specifiers exceed the limit"""
1121
def get_multiple_ranges(self, file, file_size, ranges):
1122
"""Refuses the multiple ranges request"""
1123
tcs = self.server.test_case_server
1124
if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
1126
# Emulate apache behavior
1127
self.send_error(400, "Bad Request")
1129
return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
1130
self, file, file_size, ranges)
1133
class LimitedRangeHTTPServer(http_server.HttpServer):
1134
"""An HttpServer erroring out on requests with too much range specifiers"""
1136
def __init__(self, request_handler=LimitedRangeRequestHandler,
1137
protocol_version=None,
1139
http_server.HttpServer.__init__(self, request_handler,
1140
protocol_version=protocol_version)
1141
self.range_limit = range_limit
1144
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1145
"""Tests readv requests against a server erroring out on too much ranges."""
1147
scenarios = multiply_scenarios(
1148
vary_by_http_client_implementation(),
1149
vary_by_http_protocol_version(),
1152
# Requests with more range specifiers will error out
1155
730
def create_transport_readonly_server(self):
1156
return LimitedRangeHTTPServer(range_limit=self.range_limit,
1157
protocol_version=self._protocol_version)
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())
1159
737
def setUp(self):
1160
http_utils.TestCaseWithWebserver.setUp(self)
738
TestCaseWithWebserver.setUp(self)
1161
739
# We need to manipulate ranges that correspond to real chunks in the
1162
740
# response, so we build a content appropriately.
1163
filler = ''.join(['abcdefghij' for x in range(102)])
741
filler = ''.join(['abcdefghij' for _ in range(102)])
1164
742
content = ''.join(['%04d' % v + filler for v in range(16)])
1165
743
self.build_tree_contents([('a', content)],)
1167
745
def test_few_ranges(self):
1168
t = self.get_readonly_transport()
746
t = self.get_transport()
1169
747
l = list(t.readv('a', ((0, 4), (1024, 4), )))
1170
748
self.assertEqual(l[0], (0, '0000'))
1171
749
self.assertEqual(l[1], (1024, '0001'))
1172
750
self.assertEqual(1, self.get_readonly_server().GET_request_nb)
1174
def test_more_ranges(self):
1175
t = self.get_readonly_transport()
752
def test_a_lot_of_ranges(self):
753
t = self.get_transport()
1176
754
l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
1177
755
self.assertEqual(l[0], (0, '0000'))
1178
756
self.assertEqual(l[1], (1024, '0001'))
1179
757
self.assertEqual(l[2], (4096, '0004'))
1180
758
self.assertEqual(l[3], (8192, '0008'))
1181
759
# The server will refuse to serve the first request (too much ranges),
1182
# a second request will succeed.
760
# a second request will succeeds.
1183
761
self.assertEqual(2, self.get_readonly_server().GET_request_nb)
1186
class TestHttpProxyWhiteBox(tests.TestCase):
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):
1187
779
"""Whitebox test proxy http authorization.
1189
781
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)
1192
799
def _proxied_request(self):
1193
handler = _urllib2_wrappers.ProxyHandler()
1194
request = _urllib2_wrappers.Request('GET', 'http://baz/buzzle')
800
handler = ProxyHandler(PasswordManager())
801
request = Request('GET','http://baz/buzzle')
1195
802
handler.set_proxy(request, 'http')
1198
def assertEvaluateProxyBypass(self, expected, host, no_proxy):
1199
handler = _urllib2_wrappers.ProxyHandler()
1200
self.assertEquals(expected,
1201
handler.evaluate_proxy_bypass(host, no_proxy))
1203
805
def test_empty_user(self):
1204
self.overrideEnv('http_proxy', 'http://bar.com')
1205
request = self._proxied_request()
1206
self.assertFalse(request.headers.has_key('Proxy-authorization'))
1208
def test_user_with_at(self):
1209
self.overrideEnv('http_proxy',
1210
'http://username@domain:password@proxy_host:1234')
806
self._install_env({'http_proxy': 'http://bar.com'})
1211
807
request = self._proxied_request()
1212
808
self.assertFalse(request.headers.has_key('Proxy-authorization'))
1214
810
def test_invalid_proxy(self):
1215
811
"""A proxy env variable without scheme"""
1216
self.overrideEnv('http_proxy', 'host:1234')
812
self._install_env({'http_proxy': 'host:1234'})
1217
813
self.assertRaises(errors.InvalidURL, self._proxied_request)
1219
def test_evaluate_proxy_bypass_true(self):
1220
"""The host is not proxied"""
1221
self.assertEvaluateProxyBypass(True, 'example.com', 'example.com')
1222
self.assertEvaluateProxyBypass(True, 'bzr.example.com', '*example.com')
1224
def test_evaluate_proxy_bypass_false(self):
1225
"""The host is proxied"""
1226
self.assertEvaluateProxyBypass(False, 'bzr.example.com', None)
1228
def test_evaluate_proxy_bypass_unknown(self):
1229
"""The host is not explicitly proxied"""
1230
self.assertEvaluateProxyBypass(None, 'example.com', 'not.example.com')
1231
self.assertEvaluateProxyBypass(None, 'bzr.example.com', 'example.com')
1233
def test_evaluate_proxy_bypass_empty_entries(self):
1234
"""Ignore empty entries"""
1235
self.assertEvaluateProxyBypass(None, 'example.com', '')
1236
self.assertEvaluateProxyBypass(None, 'example.com', ',')
1237
self.assertEvaluateProxyBypass(None, 'example.com', 'foo,,bar')
1240
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
816
class TestProxyHttpServer(object):
1241
817
"""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.
1243
826
Be aware that we do not setup a real proxy here. Instead, we
1244
827
check that the *connection* goes through the proxy by serving
1245
828
different content (the faked proxy server append '-proxied'
1246
829
to the file names).
1249
scenarios = multiply_scenarios(
1250
vary_by_http_client_implementation(),
1251
vary_by_http_protocol_version(),
1254
832
# FIXME: We don't have an https server available, so we don't
1255
# test https connections. --vila toolongago
833
# test https connections.
835
# FIXME: Once the test suite is better fitted to test
836
# authorization schemes, test proxy authorizations too (see
1257
839
def setUp(self):
1258
super(TestProxyHttpServer, self).setUp()
1259
self.transport_secondary_server = http_utils.ProxyServer
840
TestCaseWithTwoWebservers.setUp(self)
1260
841
self.build_tree_contents([('foo', 'contents of foo\n'),
1261
842
('foo-proxied', 'proxied contents of foo\n')])
1262
843
# Let's setup some attributes for tests
1263
server = self.get_readonly_server()
1264
self.server_host_port = '%s:%d' % (server.host, server.port)
1265
if self._testing_pycurl():
1266
# Oh my ! pycurl does not check for the port as part of
1267
# no_proxy :-( So we just test the host part
1268
self.no_proxy_host = server.host
1270
self.no_proxy_host = self.server_host_port
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
1271
847
# The secondary server is the proxy
1272
self.proxy_url = self.get_secondary_url()
1274
def _testing_pycurl(self):
1275
# TODO: This is duplicated for lots of the classes in this file
1276
return (features.pycurl.available()
1277
and self._transport == PyCurlTransport)
1279
def assertProxied(self):
1280
t = self.get_readonly_transport()
1281
self.assertEqual('proxied contents of foo\n', t.get('foo').read())
1283
def assertNotProxied(self):
1284
t = self.get_readonly_transport()
1285
self.assertEqual('contents of foo\n', t.get('foo').read())
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')
1287
884
def test_http_proxy(self):
1288
self.overrideEnv('http_proxy', self.proxy_url)
1289
self.assertProxied()
885
self.proxied_in_env({'http_proxy': self.proxy_url})
1291
887
def test_HTTP_PROXY(self):
1292
if self._testing_pycurl():
1293
# pycurl does not check HTTP_PROXY for security reasons
1294
# (for use in a CGI context that we do not care
1295
# about. Should we ?)
1296
raise tests.TestNotApplicable(
1297
'pycurl does not check HTTP_PROXY for security reasons')
1298
self.overrideEnv('HTTP_PROXY', self.proxy_url)
1299
self.assertProxied()
888
self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
1301
890
def test_all_proxy(self):
1302
self.overrideEnv('all_proxy', self.proxy_url)
1303
self.assertProxied()
891
self.proxied_in_env({'all_proxy': self.proxy_url})
1305
893
def test_ALL_PROXY(self):
1306
self.overrideEnv('ALL_PROXY', self.proxy_url)
1307
self.assertProxied()
894
self.proxied_in_env({'ALL_PROXY': self.proxy_url})
1309
896
def test_http_proxy_with_no_proxy(self):
1310
self.overrideEnv('no_proxy', self.no_proxy_host)
1311
self.overrideEnv('http_proxy', self.proxy_url)
1312
self.assertNotProxied()
897
self.not_proxied_in_env({'http_proxy': self.proxy_url,
898
'no_proxy': self.no_proxy_host})
1314
900
def test_HTTP_PROXY_with_NO_PROXY(self):
1315
if self._testing_pycurl():
1316
raise tests.TestNotApplicable(
1317
'pycurl does not check HTTP_PROXY for security reasons')
1318
self.overrideEnv('NO_PROXY', self.no_proxy_host)
1319
self.overrideEnv('HTTP_PROXY', self.proxy_url)
1320
self.assertNotProxied()
901
self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
902
'NO_PROXY': self.no_proxy_host})
1322
904
def test_all_proxy_with_no_proxy(self):
1323
self.overrideEnv('no_proxy', self.no_proxy_host)
1324
self.overrideEnv('all_proxy', self.proxy_url)
1325
self.assertNotProxied()
905
self.not_proxied_in_env({'all_proxy': self.proxy_url,
906
'no_proxy': self.no_proxy_host})
1327
908
def test_ALL_PROXY_with_NO_PROXY(self):
1328
self.overrideEnv('NO_PROXY', self.no_proxy_host)
1329
self.overrideEnv('ALL_PROXY', self.proxy_url)
1330
self.assertNotProxied()
1332
def test_http_proxy_without_scheme(self):
1333
self.overrideEnv('http_proxy', self.server_host_port)
1334
if self._testing_pycurl():
1335
# pycurl *ignores* invalid proxy env variables. If that ever change
1336
# in the future, this test will fail indicating that pycurl do not
1337
# ignore anymore such variables.
1338
self.assertNotProxied()
1340
self.assertRaises(errors.InvalidURL, self.assertProxied)
1343
class TestRanges(http_utils.TestCaseWithWebserver):
1344
"""Test the Range header in GET methods."""
1346
scenarios = multiply_scenarios(
1347
vary_by_http_client_implementation(),
1348
vary_by_http_protocol_version(),
1352
http_utils.TestCaseWithWebserver.setUp(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)
1353
966
self.build_tree_contents([('a', '0123456789')],)
1355
def create_transport_readonly_server(self):
1356
return http_server.HttpServer(protocol_version=self._protocol_version)
967
server = self.get_readonly_server()
968
self.transport = self._transport(server.get_url())
1358
970
def _file_contents(self, relpath, ranges):
1359
t = self.get_readonly_transport()
1360
971
offsets = [ (start, end - start + 1) for start, end in ranges]
1361
coalesce = t._coalesce_offsets
972
coalesce = self.transport._coalesce_offsets
1362
973
coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
1363
code, data = t._get(relpath, coalesced)
974
code, data = self.transport._get(relpath, coalesced)
1364
975
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1365
976
for start, end in ranges:
1366
977
data.seek(start)
1367
978
yield data.read(end - start + 1)
1369
980
def _file_tail(self, relpath, tail_amount):
1370
t = self.get_readonly_transport()
1371
code, data = t._get(relpath, [], tail_amount)
981
code, data = self.transport._get(relpath, [], tail_amount)
1372
982
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1373
data.seek(-tail_amount, 2)
983
data.seek(-tail_amount + 1, 2)
1374
984
return data.read(tail_amount)
1376
986
def test_range_header(self):
1378
988
map(self.assertEqual,['0', '234'],
1379
989
list(self._file_contents('a', [(0,0), (2,4)])),)
1381
def test_range_header_tail(self):
1382
991
self.assertEqual('789', self._file_tail('a', 3))
1384
def test_syntactically_invalid_range_header(self):
1385
self.assertListRaises(errors.InvalidHttpRange,
992
# Syntactically invalid range
993
self.assertListRaises(errors.InvalidRange,
1386
994
self._file_contents, 'a', [(4, 3)])
1388
def test_semantically_invalid_range_header(self):
1389
self.assertListRaises(errors.InvalidHttpRange,
995
# Semantically invalid range
996
self.assertListRaises(errors.InvalidRange,
1390
997
self._file_contents, 'a', [(42, 128)])
1393
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1394
"""Test redirection between http servers."""
1396
scenarios = multiply_scenarios(
1397
vary_by_http_client_implementation(),
1398
vary_by_http_protocol_version(),
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)
1401
1031
def setUp(self):
1402
1032
super(TestHTTPRedirections, self).setUp()
1849
1335
('b-proxied', 'contents of b\n'),
1852
def get_user_transport(self, user, password):
1853
self.overrideEnv('all_proxy', self.get_user_url(user, password))
1854
return TestAuth.get_user_transport(self, user, password)
1856
def test_empty_pass(self):
1857
if self._testing_pycurl():
1859
if pycurl.version_info()[1] < '7.16.0':
1861
'pycurl < 7.16.0 does not handle empty proxy passwords')
1862
super(TestProxyAuth, self).test_empty_pass()
1865
class SampleSocket(object):
1866
"""A socket-like object for use in testing the HTTP request handler."""
1868
def __init__(self, socket_read_content):
1869
"""Constructs a sample socket.
1871
:param socket_read_content: a byte sequence
1873
# Use plain python StringIO so we can monkey-patch the close method to
1874
# not discard the contents.
1875
from StringIO import StringIO
1876
self.readfile = StringIO(socket_read_content)
1877
self.writefile = StringIO()
1878
self.writefile.close = lambda: None
1879
self.close = lambda: None
1881
def makefile(self, mode='r', bufsize=None):
1883
return self.readfile
1885
return self.writefile
1888
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1890
scenarios = multiply_scenarios(
1891
vary_by_http_client_implementation(),
1892
vary_by_http_protocol_version(),
1896
super(SmartHTTPTunnellingTest, self).setUp()
1897
# We use the VFS layer as part of HTTP tunnelling tests.
1898
self.overrideEnv('BZR_NO_SMART_VFS', None)
1899
self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1900
self.http_server = self.get_readonly_server()
1902
def create_transport_readonly_server(self):
1903
server = http_utils.HTTPServerWithSmarts(
1904
protocol_version=self._protocol_version)
1905
server._url_protocol = self._url_protocol
1908
def test_open_bzrdir(self):
1909
branch = self.make_branch('relpath')
1910
url = self.http_server.get_url() + 'relpath'
1911
bd = bzrdir.BzrDir.open(url)
1912
self.addCleanup(bd.transport.disconnect)
1913
self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1915
def test_bulk_data(self):
1916
# We should be able to send and receive bulk data in a single message.
1917
# The 'readv' command in the smart protocol both sends and receives
1918
# bulk data, so we use that.
1919
self.build_tree(['data-file'])
1920
http_transport = transport.get_transport_from_url(
1921
self.http_server.get_url())
1922
medium = http_transport.get_smart_medium()
1923
# Since we provide the medium, the url below will be mostly ignored
1924
# during the test, as long as the path is '/'.
1925
remote_transport = remote.RemoteTransport('bzr://fake_host/',
1928
[(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
1930
def test_http_send_smart_request(self):
1932
post_body = 'hello\n'
1933
expected_reply_body = 'ok\x012\n'
1935
http_transport = transport.get_transport_from_url(
1936
self.http_server.get_url())
1937
medium = http_transport.get_smart_medium()
1938
response = medium.send_http_smart_request(post_body)
1939
reply_body = response.read()
1940
self.assertEqual(expected_reply_body, reply_body)
1942
def test_smart_http_server_post_request_handler(self):
1943
httpd = self.http_server.server
1945
socket = SampleSocket(
1946
'POST /.bzr/smart %s \r\n' % self._protocol_version
1947
# HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
1949
+ 'Content-Length: 6\r\n'
1952
# Beware: the ('localhost', 80) below is the
1953
# client_address parameter, but we don't have one because
1954
# we have defined a socket which is not bound to an
1955
# address. The test framework never uses this client
1956
# address, so far...
1957
request_handler = http_utils.SmartRequestHandler(socket,
1960
response = socket.writefile.getvalue()
1961
self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
1962
# This includes the end of the HTTP headers, and all the body.
1963
expected_end_of_response = '\r\n\r\nok\x012\n'
1964
self.assertEndsWith(response, expected_end_of_response)
1967
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
1968
"""No smart server here request handler."""
1971
self.send_error(403, "Forbidden")
1974
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
1975
"""Test smart client behaviour against an http server without smarts."""
1977
_req_handler_class = ForbiddenRequestHandler
1979
def test_probe_smart_server(self):
1980
"""Test error handling against server refusing smart requests."""
1981
t = self.get_readonly_transport()
1982
# No need to build a valid smart request here, the server will not even
1983
# try to interpret it.
1984
self.assertRaises(errors.SmartProtocolError,
1985
t.get_smart_medium().send_http_smart_request,
1989
class Test_redirected_to(tests.TestCase):
1991
scenarios = vary_by_http_client_implementation()
1993
def test_redirected_to_subdir(self):
1994
t = self._transport('http://www.example.com/foo')
1995
r = t._redirected_to('http://www.example.com/foo',
1996
'http://www.example.com/foo/subdir')
1997
self.assertIsInstance(r, type(t))
1998
# Both transports share the some connection
1999
self.assertEqual(t._get_connection(), r._get_connection())
2000
self.assertEquals('http://www.example.com/foo/subdir/', r.base)
2002
def test_redirected_to_self_with_slash(self):
2003
t = self._transport('http://www.example.com/foo')
2004
r = t._redirected_to('http://www.example.com/foo',
2005
'http://www.example.com/foo/')
2006
self.assertIsInstance(r, type(t))
2007
# Both transports share the some connection (one can argue that we
2008
# should return the exact same transport here, but that seems
2010
self.assertEqual(t._get_connection(), r._get_connection())
2012
def test_redirected_to_host(self):
2013
t = self._transport('http://www.example.com/foo')
2014
r = t._redirected_to('http://www.example.com/foo',
2015
'http://foo.example.com/foo/subdir')
2016
self.assertIsInstance(r, type(t))
2017
self.assertEquals('http://foo.example.com/foo/subdir/',
2020
def test_redirected_to_same_host_sibling_protocol(self):
2021
t = self._transport('http://www.example.com/foo')
2022
r = t._redirected_to('http://www.example.com/foo',
2023
'https://www.example.com/foo')
2024
self.assertIsInstance(r, type(t))
2025
self.assertEquals('https://www.example.com/foo/',
2028
def test_redirected_to_same_host_different_protocol(self):
2029
t = self._transport('http://www.example.com/foo')
2030
r = t._redirected_to('http://www.example.com/foo',
2031
'ftp://www.example.com/foo')
2032
self.assertNotEquals(type(r), type(t))
2033
self.assertEquals('ftp://www.example.com/foo/', r.external_url())
2035
def test_redirected_to_same_host_specific_implementation(self):
2036
t = self._transport('http://www.example.com/foo')
2037
r = t._redirected_to('http://www.example.com/foo',
2038
'https+urllib://www.example.com/foo')
2039
self.assertEquals('https://www.example.com/foo/', r.external_url())
2041
def test_redirected_to_different_host_same_user(self):
2042
t = self._transport('http://joe@www.example.com/foo')
2043
r = t._redirected_to('http://www.example.com/foo',
2044
'https://foo.example.com/foo')
2045
self.assertIsInstance(r, type(t))
2046
self.assertEqual(t._parsed_url.user, r._parsed_url.user)
2047
self.assertEquals('https://joe@foo.example.com/foo/', r.external_url())
2050
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
2051
"""Request handler for a unique and pre-defined request.
2053
The only thing we care about here is how many bytes travel on the wire. But
2054
since we want to measure it for a real http client, we have to send it
2057
We expect to receive a *single* request nothing more (and we won't even
2058
check what request it is, we just measure the bytes read until an empty
2062
def _handle_one_request(self):
2063
tcs = self.server.test_case_server
2064
requestline = self.rfile.readline()
2065
headers = self.MessageClass(self.rfile, 0)
2066
# We just read: the request, the headers, an empty line indicating the
2067
# end of the headers.
2068
bytes_read = len(requestline)
2069
for line in headers.headers:
2070
bytes_read += len(line)
2071
bytes_read += len('\r\n')
2072
if requestline.startswith('POST'):
2073
# The body should be a single line (or we don't know where it ends
2074
# and we don't want to issue a blocking read)
2075
body = self.rfile.readline()
2076
bytes_read += len(body)
2077
tcs.bytes_read = bytes_read
2079
# We set the bytes written *before* issuing the write, the client is
2080
# supposed to consume every produced byte *before* checking that value.
2082
# Doing the oppposite may lead to test failure: we may be interrupted
2083
# after the write but before updating the value. The client can then
2084
# continue and read the value *before* we can update it. And yes,
2085
# this has been observed -- vila 20090129
2086
tcs.bytes_written = len(tcs.canned_response)
2087
self.wfile.write(tcs.canned_response)
2090
class ActivityServerMixin(object):
2092
def __init__(self, protocol_version):
2093
super(ActivityServerMixin, self).__init__(
2094
request_handler=PredefinedRequestHandler,
2095
protocol_version=protocol_version)
2096
# Bytes read and written by the server
2098
self.bytes_written = 0
2099
self.canned_response = None
2102
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
2106
if features.HTTPSServerFeature.available():
2107
from bzrlib.tests import https_server
2108
class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
2112
class TestActivityMixin(object):
2113
"""Test socket activity reporting.
2115
We use a special purpose server to control the bytes sent and received and
2116
be able to predict the activity on the client socket.
2120
tests.TestCase.setUp(self)
2121
self.server = self._activity_server(self._protocol_version)
2122
self.server.start_server()
2123
_activities = {} # Don't close over self and create a cycle
2124
def report_activity(t, bytes, direction):
2125
count = _activities.get(direction, 0)
2127
_activities[direction] = count
2128
self.activities = _activities
2130
# We override at class level because constructors may propagate the
2131
# bound method and render instance overriding ineffective (an
2132
# alternative would be to define a specific ui factory instead...)
2133
self.overrideAttr(self._transport, '_report_activity', report_activity)
2134
self.addCleanup(self.server.stop_server)
2136
def get_transport(self):
2137
t = self._transport(self.server.get_url())
2138
# FIXME: Needs cleanup -- vila 20100611
2141
def assertActivitiesMatch(self):
2142
self.assertEqual(self.server.bytes_read,
2143
self.activities.get('write', 0), 'written bytes')
2144
self.assertEqual(self.server.bytes_written,
2145
self.activities.get('read', 0), 'read bytes')
2148
self.server.canned_response = '''HTTP/1.1 200 OK\r
2149
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2150
Server: Apache/2.0.54 (Fedora)\r
2151
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2152
ETag: "56691-23-38e9ae00"\r
2153
Accept-Ranges: bytes\r
2154
Content-Length: 35\r
2156
Content-Type: text/plain; charset=UTF-8\r
2158
Bazaar-NG meta directory, format 1
2160
t = self.get_transport()
2161
self.assertEqual('Bazaar-NG meta directory, format 1\n',
2162
t.get('foo/bar').read())
2163
self.assertActivitiesMatch()
2166
self.server.canned_response = '''HTTP/1.1 200 OK\r
2167
Server: SimpleHTTP/0.6 Python/2.5.2\r
2168
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
2169
Content-type: application/octet-stream\r
2170
Content-Length: 20\r
2171
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
2174
t = self.get_transport()
2175
self.assertTrue(t.has('foo/bar'))
2176
self.assertActivitiesMatch()
2178
def test_readv(self):
2179
self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
2180
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
2181
Server: Apache/2.0.54 (Fedora)\r
2182
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
2183
ETag: "238a3c-16ec2-805c5540"\r
2184
Accept-Ranges: bytes\r
2185
Content-Length: 1534\r
2187
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
2190
--418470f848b63279b\r
2191
Content-type: text/plain; charset=UTF-8\r
2192
Content-range: bytes 0-254/93890\r
2194
mbp@sourcefrog.net-20050309040815-13242001617e4a06
2195
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
2196
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
2197
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
2198
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
2200
--418470f848b63279b\r
2201
Content-type: text/plain; charset=UTF-8\r
2202
Content-range: bytes 1000-2049/93890\r
2205
mbp@sourcefrog.net-20050311063625-07858525021f270b
2206
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
2207
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
2208
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
2209
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
2210
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
2211
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
2212
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
2213
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
2214
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
2215
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
2216
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
2217
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
2218
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
2219
mbp@sourcefrog.net-20050313120651-497bd231b19df600
2220
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
2221
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
2222
mbp@sourcefrog.net-20050314025539-637a636692c055cf
2223
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
2224
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
2226
--418470f848b63279b--\r
2228
t = self.get_transport()
2229
# Remember that the request is ignored and that the ranges below
2230
# doesn't have to match the canned response.
2231
l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
2232
self.assertEqual(2, len(l))
2233
self.assertActivitiesMatch()
2235
def test_post(self):
2236
self.server.canned_response = '''HTTP/1.1 200 OK\r
2237
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2238
Server: Apache/2.0.54 (Fedora)\r
2239
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2240
ETag: "56691-23-38e9ae00"\r
2241
Accept-Ranges: bytes\r
2242
Content-Length: 35\r
2244
Content-Type: text/plain; charset=UTF-8\r
2246
lalala whatever as long as itsssss
2248
t = self.get_transport()
2249
# We must send a single line of body bytes, see
2250
# PredefinedRequestHandler._handle_one_request
2251
code, f = t._post('abc def end-of-body\n')
2252
self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2253
self.assertActivitiesMatch()
2256
class TestActivity(tests.TestCase, TestActivityMixin):
2258
scenarios = multiply_scenarios(
2259
vary_by_http_activity(),
2260
vary_by_http_protocol_version(),
2264
TestActivityMixin.setUp(self)
2267
class TestNoReportActivity(tests.TestCase, TestActivityMixin):
2269
# Unlike TestActivity, we are really testing ReportingFileSocket and
2270
# ReportingSocket, so we don't need all the parametrization. Since
2271
# ReportingFileSocket and ReportingSocket are wrappers, it's easier to
2272
# test them through their use by the transport than directly (that's a
2273
# bit less clean but far more simpler and effective).
2274
_activity_server = ActivityHTTPServer
2275
_protocol_version = 'HTTP/1.1'
2278
self._transport =_urllib.HttpTransport_urllib
2279
TestActivityMixin.setUp(self)
2281
def assertActivitiesMatch(self):
2282
# Nothing to check here
2286
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
2287
"""Test authentication on the redirected http server."""
2289
scenarios = vary_by_http_protocol_version()
2291
_auth_header = 'Authorization'
2292
_password_prompt_prefix = ''
2293
_username_prompt_prefix = ''
2294
_auth_server = http_utils.HTTPBasicAuthServer
2295
_transport = _urllib.HttpTransport_urllib
2298
super(TestAuthOnRedirected, self).setUp()
2299
self.build_tree_contents([('a','a'),
2301
('1/a', 'redirected once'),
2303
new_prefix = 'http://%s:%s' % (self.new_server.host,
2304
self.new_server.port)
2305
self.old_server.redirections = [
2306
('(.*)', r'%s/1\1' % (new_prefix), 301),]
2307
self.old_transport = self.get_old_transport()
2308
self.new_server.add_user('joe', 'foo')
2309
cleanup_http_redirection_connections(self)
2311
def create_transport_readonly_server(self):
2312
server = self._auth_server(protocol_version=self._protocol_version)
2313
server._url_protocol = self._url_protocol
2319
def test_auth_on_redirected_via_do_catching_redirections(self):
2320
self.redirections = 0
2322
def redirected(t, exception, redirection_notice):
2323
self.redirections += 1
2324
redirected_t = t._redirected_to(exception.source, exception.target)
2325
self.addCleanup(redirected_t.disconnect)
2328
stdout = tests.StringIOWrapper()
2329
stderr = tests.StringIOWrapper()
2330
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
2331
stdout=stdout, stderr=stderr)
2332
self.assertEqual('redirected once',
2333
transport.do_catching_redirections(
2334
self.get_a, self.old_transport, redirected).read())
2335
self.assertEqual(1, self.redirections)
2336
# stdin should be empty
2337
self.assertEqual('', ui.ui_factory.stdin.readline())
2338
# stdout should be empty, stderr will contains the prompts
2339
self.assertEqual('', stdout.getvalue())
2341
def test_auth_on_redirected_via_following_redirections(self):
2342
self.new_server.add_user('joe', 'foo')
2343
stdout = tests.StringIOWrapper()
2344
stderr = tests.StringIOWrapper()
2345
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
2346
stdout=stdout, stderr=stderr)
2347
t = self.old_transport
2348
req = RedirectedRequest('GET', t.abspath('a'))
2349
new_prefix = 'http://%s:%s' % (self.new_server.host,
2350
self.new_server.port)
2351
self.old_server.redirections = [
2352
('(.*)', r'%s/1\1' % (new_prefix), 301),]
2353
self.assertEqual('redirected once', t._perform(req).read())
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())
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()