185
110
self.received_bytes = ''
189
return '%s://%s:%s/' % (self.scheme, self.host, self.port)
191
def start_server(self):
192
113
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
193
114
self._sock.bind(('127.0.0.1', 0))
194
115
self.host, self.port = self._sock.getsockname()
195
116
self._ready = threading.Event()
196
self._thread = test_server.TestThread(
197
sync_event=self._ready, target=self._accept_read_and_reply)
117
self._thread = threading.Thread(target=self._accept_read_and_reply)
118
self._thread.setDaemon(True)
198
119
self._thread.start()
199
if 'threads' in tests.selftest_debug_flags:
200
sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
203
122
def _accept_read_and_reply(self):
204
123
self._sock.listen(1)
205
124
self._ready.set()
206
conn, address = self._sock.accept()
207
if self._expect_body_tail is not None:
125
self._sock.settimeout(5)
127
conn, address = self._sock.accept()
128
# On win32, the accepted connection will be non-blocking to start
129
# with because we're using settimeout.
130
conn.setblocking(True)
208
131
while not self.received_bytes.endswith(self._expect_body_tail):
209
132
self.received_bytes += conn.recv(4096)
210
133
conn.sendall('HTTP/1.1 200 OK\r\n')
134
except socket.timeout:
135
# Make sure the client isn't stuck waiting for us to e.g. accept.
212
136
self._sock.close()
213
137
except socket.error:
214
138
# 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
144
except socket.error:
224
145
# 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
151
class TestWithTransport_pycurl(object):
372
152
"""Test case to inherit from if pycurl is present"""
374
154
def _get_pycurl_maybe(self):
375
self.requireFeature(features.pycurl)
376
return PyCurlTransport
156
from bzrlib.transport.http._pycurl import PyCurlTransport
157
return PyCurlTransport
158
except errors.DependencyNotPresent:
159
raise TestSkipped('pycurl not present')
378
161
_transport = property(_get_pycurl_maybe)
381
class TestHttpUrls(tests.TestCase):
164
class TestHttpUrls(TestCase):
383
166
# TODO: This should be moved to authorization tests once they
386
169
def test_url_parsing(self):
387
170
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()
171
url = extract_auth('http://example.com', f)
172
self.assertEquals('http://example.com', url)
173
self.assertEquals(0, len(f.credentials))
174
url = extract_auth('http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
175
self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
176
self.assertEquals(1, len(f.credentials))
177
self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
181
class TestHttpTransportUrls(object):
182
"""Test the http urls.
184
This MUST be used by daughter classes that also inherit from
187
We can't inherit directly from TestCase or the
188
test framework will try to create an instance which cannot
189
run, its implementation being incomplete.
404
192
def test_abs_url(self):
405
193
"""Construction of absolute http URLs"""
406
t = self._transport('http://example.com/bzr/bzr.dev/')
194
t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
407
195
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')
196
eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
197
eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
198
eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
411
199
eq(t.abspath('.bzr/1//2/./3'),
412
'http://example.com/bzr/bzr.dev/.bzr/1/2/3')
200
'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
414
202
def test_invalid_http_urls(self):
415
203
"""Trap invalid construction of urls"""
416
self._transport('http://example.com/bzr/bzr.dev/')
417
self.assertRaises(errors.InvalidURL,
419
'http://http://example.com/bzr/bzr.dev/')
204
t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
205
self.assertRaises(ValueError, t.abspath, '.bzr/')
206
t = self._transport('http://http://bazaar-vcs.org/bzr/bzr.dev/')
207
self.assertRaises((errors.InvalidURL, errors.ConnectionError),
421
210
def test_http_root_urls(self):
422
211
"""Construction of URLs from server root"""
423
t = self._transport('http://example.com/')
212
t = self._transport('http://bzr.ozlabs.org/')
424
213
eq = self.assertEqualDiff
425
214
eq(t.abspath('.bzr/tree-version'),
426
'http://example.com/.bzr/tree-version')
215
'http://bzr.ozlabs.org/.bzr/tree-version')
428
217
def test_http_impl_urls(self):
429
218
"""There are servers which ask for particular clients to connect"""
430
219
server = self._server()
431
server.start_server()
433
222
url = server.get_url()
434
self.assertTrue(url.startswith('%s://' % self._url_protocol))
223
self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
439
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
228
class TestHttpUrls_urllib(TestHttpTransportUrls, TestCase):
229
"""Test http urls with urllib"""
231
_transport = HttpTransport_urllib
232
_server = HttpServer_urllib
233
_qualified_prefix = 'http+urllib'
236
class TestHttpUrls_pycurl(TestWithTransport_pycurl, TestHttpTransportUrls,
238
"""Test http urls with pycurl"""
240
_server = HttpServer_PyCurl
241
_qualified_prefix = 'http+pycurl'
441
243
# TODO: This should really be moved into another pycurl
442
244
# specific test. When https tests will be implemented, take
826
673
self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
827
674
t.readv, 'a', [(12,2)])
829
def test_readv_multiple_get_requests(self):
830
server = self.get_readonly_server()
831
t = self.get_readonly_transport()
832
# force transport to issue multiple requests
833
t._max_readv_combine = 1
834
t._max_get_ranges = 1
835
l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
836
self.assertEqual(l[0], (0, '0'))
837
self.assertEqual(l[1], (1, '1'))
838
self.assertEqual(l[2], (3, '34'))
839
self.assertEqual(l[3], (9, '9'))
840
# The server should have issued 4 requests
841
self.assertEqual(4, server.GET_request_nb)
843
def test_readv_get_max_size(self):
844
server = self.get_readonly_server()
845
t = self.get_readonly_transport()
846
# force transport to issue multiple requests by limiting the number of
847
# bytes by request. Note that this apply to coalesced offsets only, a
848
# single range will keep its size even if bigger than the limit.
850
l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
851
self.assertEqual(l[0], (0, '0'))
852
self.assertEqual(l[1], (1, '1'))
853
self.assertEqual(l[2], (2, '2345'))
854
self.assertEqual(l[3], (6, '6789'))
855
# The server should have issued 3 requests
856
self.assertEqual(3, server.GET_request_nb)
858
def test_complete_readv_leave_pipe_clean(self):
859
server = self.get_readonly_server()
860
t = self.get_readonly_transport()
861
# force transport to issue multiple requests
863
list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
864
# The server should have issued 3 requests
865
self.assertEqual(3, server.GET_request_nb)
866
self.assertEqual('0123456789', t.get_bytes('a'))
867
self.assertEqual(4, server.GET_request_nb)
869
def test_incomplete_readv_leave_pipe_clean(self):
870
server = self.get_readonly_server()
871
t = self.get_readonly_transport()
872
# force transport to issue multiple requests
874
# Don't collapse readv results into a list so that we leave unread
875
# bytes on the socket
876
ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
877
self.assertEqual((0, '0'), ireadv.next())
878
# The server should have issued one request so far
879
self.assertEqual(1, server.GET_request_nb)
880
self.assertEqual('0123456789', t.get_bytes('a'))
881
# get_bytes issued an additional request, the readv pending ones are
883
self.assertEqual(2, server.GET_request_nb)
886
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
887
"""Always reply to range request as if they were single.
889
Don't be explicit about it, just to annoy the clients.
892
def get_multiple_ranges(self, file, file_size, ranges):
893
"""Answer as if it was a single range request and ignores the rest"""
894
(start, end) = ranges[0]
895
return self.get_single_range(file, file_size, start, end)
898
677
class TestSingleRangeRequestServer(TestRangeRequestServer):
899
678
"""Test readv against a server which accept only single range requests"""
901
_req_handler_class = SingleRangeRequestHandler
904
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
905
"""Only reply to simple range requests, errors out on multiple"""
907
def get_multiple_ranges(self, file, file_size, ranges):
908
"""Refuses the multiple ranges request"""
911
self.send_error(416, "Requested range not satisfiable")
913
(start, end) = ranges[0]
914
return self.get_single_range(file, file_size, start, end)
680
def create_transport_readonly_server(self):
681
return HttpServer(SingleRangeRequestHandler)
684
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
685
TestCaseWithWebserver):
686
"""Tests single range requests accepting server for urllib implementation"""
688
_transport = HttpTransport_urllib
691
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
692
TestSingleRangeRequestServer,
693
TestCaseWithWebserver):
694
"""Tests single range requests accepting server for pycurl implementation"""
917
697
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
918
698
"""Test readv against a server which only accept single range requests"""
920
_req_handler_class = SingleOnlyRangeRequestHandler
923
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
924
"""Ignore range requests without notice"""
927
# Update the statistics
928
self.server.test_case_server.GET_request_nb += 1
929
# Just bypass the range handling done by TestingHTTPRequestHandler
930
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
700
def create_transport_readonly_server(self):
701
return HttpServer(SingleOnlyRangeRequestHandler)
704
class TestSingleOnlyRangeRequestServer_urllib(TestSingleOnlyRangeRequestServer,
705
TestCaseWithWebserver):
706
"""Tests single range requests accepting server for urllib implementation"""
708
_transport = HttpTransport_urllib
711
class TestSingleOnlyRangeRequestServer_pycurl(TestWithTransport_pycurl,
712
TestSingleOnlyRangeRequestServer,
713
TestCaseWithWebserver):
714
"""Tests single range requests accepting server for pycurl implementation"""
933
717
class TestNoRangeRequestServer(TestRangeRequestServer):
934
718
"""Test readv against a server which do not accept range requests"""
936
_req_handler_class = NoRangeRequestHandler
939
class MultipleRangeWithoutContentLengthRequestHandler(
940
http_server.TestingHTTPRequestHandler):
941
"""Reply to multiple range requests without content length header."""
943
def get_multiple_ranges(self, file, file_size, ranges):
944
self.send_response(206)
945
self.send_header('Accept-Ranges', 'bytes')
946
# XXX: this is strange; the 'random' name below seems undefined and
947
# yet the tests pass -- mbp 2010-10-11 bug 658773
948
boundary = "%d" % random.randint(0,0x7FFFFFFF)
949
self.send_header("Content-Type",
950
"multipart/byteranges; boundary=%s" % boundary)
952
for (start, end) in ranges:
953
self.wfile.write("--%s\r\n" % boundary)
954
self.send_header("Content-type", 'application/octet-stream')
955
self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
959
self.send_range_content(file, start, end - start + 1)
961
self.wfile.write("--%s\r\n" % boundary)
964
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
966
_req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
969
class TruncatedMultipleRangeRequestHandler(
970
http_server.TestingHTTPRequestHandler):
971
"""Reply to multiple range requests truncating the last ones.
973
This server generates responses whose Content-Length describes all the
974
ranges, but fail to include the last ones leading to client short reads.
975
This has been observed randomly with lighttpd (bug #179368).
978
_truncated_ranges = 2
980
def get_multiple_ranges(self, file, file_size, ranges):
981
self.send_response(206)
982
self.send_header('Accept-Ranges', 'bytes')
984
self.send_header('Content-Type',
985
'multipart/byteranges; boundary=%s' % boundary)
986
boundary_line = '--%s\r\n' % boundary
987
# Calculate the Content-Length
989
for (start, end) in ranges:
990
content_length += len(boundary_line)
991
content_length += self._header_line_length(
992
'Content-type', 'application/octet-stream')
993
content_length += self._header_line_length(
994
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
995
content_length += len('\r\n') # end headers
996
content_length += end - start # + 1
997
content_length += len(boundary_line)
998
self.send_header('Content-length', content_length)
1001
# Send the multipart body
1003
for (start, end) in ranges:
1004
self.wfile.write(boundary_line)
1005
self.send_header('Content-type', 'application/octet-stream')
1006
self.send_header('Content-Range', 'bytes %d-%d/%d'
1007
% (start, end, file_size))
1009
if cur + self._truncated_ranges >= len(ranges):
1010
# Abruptly ends the response and close the connection
1011
self.close_connection = 1
1013
self.send_range_content(file, start, end - start + 1)
1016
self.wfile.write(boundary_line)
1019
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
1021
_req_handler_class = TruncatedMultipleRangeRequestHandler
1024
super(TestTruncatedMultipleRangeServer, self).setUp()
1025
self.build_tree_contents([('a', '0123456789')],)
1027
def test_readv_with_short_reads(self):
1028
server = self.get_readonly_server()
1029
t = self.get_readonly_transport()
1030
# Force separate ranges for each offset
1031
t._bytes_to_read_before_seek = 0
1032
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1033
self.assertEqual((0, '0'), ireadv.next())
1034
self.assertEqual((2, '2'), ireadv.next())
1035
if not self._testing_pycurl():
1036
# Only one request have been issued so far (except for pycurl that
1037
# try to read the whole response at once)
1038
self.assertEqual(1, server.GET_request_nb)
1039
self.assertEqual((4, '45'), ireadv.next())
1040
self.assertEqual((9, '9'), ireadv.next())
1041
# Both implementations issue 3 requests but:
1042
# - urllib does two multiple (4 ranges, then 2 ranges) then a single
1044
# - pycurl does two multiple (4 ranges, 4 ranges) then a single range
1045
self.assertEqual(3, server.GET_request_nb)
1046
# Finally the client have tried a single range request and stays in
1048
self.assertEqual('single', t._range_hint)
1051
class TruncatedBeforeBoundaryRequestHandler(
1052
http_server.TestingHTTPRequestHandler):
1053
"""Truncation before a boundary, like in bug 198646"""
1055
_truncated_ranges = 1
1057
def get_multiple_ranges(self, file, file_size, ranges):
1058
self.send_response(206)
1059
self.send_header('Accept-Ranges', 'bytes')
1061
self.send_header('Content-Type',
1062
'multipart/byteranges; boundary=%s' % boundary)
1063
boundary_line = '--%s\r\n' % boundary
1064
# Calculate the Content-Length
1066
for (start, end) in ranges:
1067
content_length += len(boundary_line)
1068
content_length += self._header_line_length(
1069
'Content-type', 'application/octet-stream')
1070
content_length += self._header_line_length(
1071
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
1072
content_length += len('\r\n') # end headers
1073
content_length += end - start # + 1
1074
content_length += len(boundary_line)
1075
self.send_header('Content-length', content_length)
1078
# Send the multipart body
1080
for (start, end) in ranges:
1081
if cur + self._truncated_ranges >= len(ranges):
1082
# Abruptly ends the response and close the connection
1083
self.close_connection = 1
1085
self.wfile.write(boundary_line)
1086
self.send_header('Content-type', 'application/octet-stream')
1087
self.send_header('Content-Range', 'bytes %d-%d/%d'
1088
% (start, end, file_size))
1090
self.send_range_content(file, start, end - start + 1)
1093
self.wfile.write(boundary_line)
1096
class TestTruncatedBeforeBoundary(TestSpecificRequestHandler):
1097
"""Tests the case of bug 198646, disconnecting before a boundary."""
1099
_req_handler_class = TruncatedBeforeBoundaryRequestHandler
1102
super(TestTruncatedBeforeBoundary, self).setUp()
1103
self.build_tree_contents([('a', '0123456789')],)
1105
def test_readv_with_short_reads(self):
1106
server = self.get_readonly_server()
1107
t = self.get_readonly_transport()
1108
# Force separate ranges for each offset
1109
t._bytes_to_read_before_seek = 0
1110
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1111
self.assertEqual((0, '0'), ireadv.next())
1112
self.assertEqual((2, '2'), ireadv.next())
1113
self.assertEqual((4, '45'), ireadv.next())
1114
self.assertEqual((9, '9'), ireadv.next())
1117
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1118
"""Errors out when range specifiers exceed the limit"""
1120
def get_multiple_ranges(self, file, file_size, ranges):
1121
"""Refuses the multiple ranges request"""
1122
tcs = self.server.test_case_server
1123
if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
1125
# Emulate apache behavior
1126
self.send_error(400, "Bad Request")
1128
return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
1129
self, file, file_size, ranges)
1132
class LimitedRangeHTTPServer(http_server.HttpServer):
1133
"""An HttpServer erroring out on requests with too much range specifiers"""
1135
def __init__(self, request_handler=LimitedRangeRequestHandler,
1136
protocol_version=None,
1138
http_server.HttpServer.__init__(self, request_handler,
1139
protocol_version=protocol_version)
1140
self.range_limit = range_limit
1143
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1144
"""Tests readv requests against a server erroring out on too much ranges."""
1146
scenarios = multiply_scenarios(
1147
vary_by_http_client_implementation(),
1148
vary_by_http_protocol_version(),
1151
# Requests with more range specifiers will error out
1154
720
def create_transport_readonly_server(self):
1155
return LimitedRangeHTTPServer(range_limit=self.range_limit,
1156
protocol_version=self._protocol_version)
1159
http_utils.TestCaseWithWebserver.setUp(self)
1160
# We need to manipulate ranges that correspond to real chunks in the
1161
# response, so we build a content appropriately.
1162
filler = ''.join(['abcdefghij' for x in range(102)])
1163
content = ''.join(['%04d' % v + filler for v in range(16)])
1164
self.build_tree_contents([('a', content)],)
1166
def test_few_ranges(self):
1167
t = self.get_readonly_transport()
1168
l = list(t.readv('a', ((0, 4), (1024, 4), )))
1169
self.assertEqual(l[0], (0, '0000'))
1170
self.assertEqual(l[1], (1024, '0001'))
1171
self.assertEqual(1, self.get_readonly_server().GET_request_nb)
1173
def test_more_ranges(self):
1174
t = self.get_readonly_transport()
1175
l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
1176
self.assertEqual(l[0], (0, '0000'))
1177
self.assertEqual(l[1], (1024, '0001'))
1178
self.assertEqual(l[2], (4096, '0004'))
1179
self.assertEqual(l[3], (8192, '0008'))
1180
# The server will refuse to serve the first request (too much ranges),
1181
# a second request will succeed.
1182
self.assertEqual(2, self.get_readonly_server().GET_request_nb)
1185
class TestHttpProxyWhiteBox(tests.TestCase):
721
return HttpServer(NoRangeRequestHandler)
724
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
725
TestCaseWithWebserver):
726
"""Tests range requests refusing server for urllib implementation"""
728
_transport = HttpTransport_urllib
731
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
732
TestNoRangeRequestServer,
733
TestCaseWithWebserver):
734
"""Tests range requests refusing server for pycurl implementation"""
737
class TestHttpProxyWhiteBox(TestCase):
1186
738
"""Whitebox test proxy http authorization.
1188
740
Only the urllib implementation is tested here.
750
def _install_env(self, env):
751
for name, value in env.iteritems():
752
self._old_env[name] = osutils.set_or_unset_env(name, value)
754
def _restore_env(self):
755
for name, value in self._old_env.iteritems():
756
osutils.set_or_unset_env(name, value)
1191
758
def _proxied_request(self):
1192
handler = _urllib2_wrappers.ProxyHandler()
1193
request = _urllib2_wrappers.Request('GET', 'http://baz/buzzle')
759
handler = ProxyHandler(PasswordManager())
760
request = Request('GET','http://baz/buzzle')
1194
761
handler.set_proxy(request, 'http')
1197
def assertEvaluateProxyBypass(self, expected, host, no_proxy):
1198
handler = _urllib2_wrappers.ProxyHandler()
1199
self.assertEquals(expected,
1200
handler.evaluate_proxy_bypass(host, no_proxy))
1202
764
def test_empty_user(self):
1203
self.overrideEnv('http_proxy', 'http://bar.com')
1204
request = self._proxied_request()
1205
self.assertFalse(request.headers.has_key('Proxy-authorization'))
1207
def test_user_with_at(self):
1208
self.overrideEnv('http_proxy',
1209
'http://username@domain:password@proxy_host:1234')
765
self._install_env({'http_proxy': 'http://bar.com'})
1210
766
request = self._proxied_request()
1211
767
self.assertFalse(request.headers.has_key('Proxy-authorization'))
1213
769
def test_invalid_proxy(self):
1214
770
"""A proxy env variable without scheme"""
1215
self.overrideEnv('http_proxy', 'host:1234')
771
self._install_env({'http_proxy': 'host:1234'})
1216
772
self.assertRaises(errors.InvalidURL, self._proxied_request)
1218
def test_evaluate_proxy_bypass_true(self):
1219
"""The host is not proxied"""
1220
self.assertEvaluateProxyBypass(True, 'example.com', 'example.com')
1221
self.assertEvaluateProxyBypass(True, 'bzr.example.com', '*example.com')
1223
def test_evaluate_proxy_bypass_false(self):
1224
"""The host is proxied"""
1225
self.assertEvaluateProxyBypass(False, 'bzr.example.com', None)
1227
def test_evaluate_proxy_bypass_unknown(self):
1228
"""The host is not explicitly proxied"""
1229
self.assertEvaluateProxyBypass(None, 'example.com', 'not.example.com')
1230
self.assertEvaluateProxyBypass(None, 'bzr.example.com', 'example.com')
1232
def test_evaluate_proxy_bypass_empty_entries(self):
1233
"""Ignore empty entries"""
1234
self.assertEvaluateProxyBypass(None, 'example.com', '')
1235
self.assertEvaluateProxyBypass(None, 'example.com', ',')
1236
self.assertEvaluateProxyBypass(None, 'example.com', 'foo,,bar')
1239
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
775
class TestProxyHttpServer(object):
1240
776
"""Tests proxy server.
778
This MUST be used by daughter classes that also inherit from
779
TestCaseWithTwoWebservers.
781
We can't inherit directly from TestCaseWithTwoWebservers or
782
the test framework will try to create an instance which
783
cannot run, its implementation being incomplete.
1242
785
Be aware that we do not setup a real proxy here. Instead, we
1243
786
check that the *connection* goes through the proxy by serving
1244
787
different content (the faked proxy server append '-proxied'
1245
788
to the file names).
1248
scenarios = multiply_scenarios(
1249
vary_by_http_client_implementation(),
1250
vary_by_http_protocol_version(),
1253
791
# FIXME: We don't have an https server available, so we don't
1254
# test https connections. --vila toolongago
792
# test https connections.
794
# FIXME: Once the test suite is better fitted to test
795
# authorization schemes, test proxy authorizations too (see
1256
798
def setUp(self):
1257
super(TestProxyHttpServer, self).setUp()
1258
self.transport_secondary_server = http_utils.ProxyServer
799
TestCaseWithTwoWebservers.setUp(self)
1259
800
self.build_tree_contents([('foo', 'contents of foo\n'),
1260
801
('foo-proxied', 'proxied contents of foo\n')])
1261
802
# Let's setup some attributes for tests
1262
server = self.get_readonly_server()
1263
self.server_host_port = '%s:%d' % (server.host, server.port)
1264
if self._testing_pycurl():
1265
# Oh my ! pycurl does not check for the port as part of
1266
# no_proxy :-( So we just test the host part
1267
self.no_proxy_host = server.host
1269
self.no_proxy_host = self.server_host_port
803
self.server = self.get_readonly_server()
804
self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
805
self.no_proxy_host = self.proxy_address
1270
806
# The secondary server is the proxy
1271
self.proxy_url = self.get_secondary_url()
1273
def _testing_pycurl(self):
1274
# TODO: This is duplicated for lots of the classes in this file
1275
return (features.pycurl.available()
1276
and self._transport == PyCurlTransport)
1278
def assertProxied(self):
1279
t = self.get_readonly_transport()
1280
self.assertEqual('proxied contents of foo\n', t.get('foo').read())
1282
def assertNotProxied(self):
1283
t = self.get_readonly_transport()
1284
self.assertEqual('contents of foo\n', t.get('foo').read())
807
self.proxy = self.get_secondary_server()
808
self.proxy_url = self.proxy.get_url()
811
def create_transport_secondary_server(self):
812
"""Creates an http server that will serve files with
813
'-proxied' appended to their names.
817
def _install_env(self, env):
818
for name, value in env.iteritems():
819
self._old_env[name] = osutils.set_or_unset_env(name, value)
821
def _restore_env(self):
822
for name, value in self._old_env.iteritems():
823
osutils.set_or_unset_env(name, value)
825
def proxied_in_env(self, env):
826
self._install_env(env)
827
url = self.server.get_url()
828
t = self._transport(url)
830
self.assertEqual(t.get('foo').read(), 'proxied contents of foo\n')
834
def not_proxied_in_env(self, env):
835
self._install_env(env)
836
url = self.server.get_url()
837
t = self._transport(url)
839
self.assertEqual(t.get('foo').read(), 'contents of foo\n')
1286
843
def test_http_proxy(self):
1287
self.overrideEnv('http_proxy', self.proxy_url)
1288
self.assertProxied()
844
self.proxied_in_env({'http_proxy': self.proxy_url})
1290
846
def test_HTTP_PROXY(self):
1291
if self._testing_pycurl():
1292
# pycurl does not check HTTP_PROXY for security reasons
1293
# (for use in a CGI context that we do not care
1294
# about. Should we ?)
1295
raise tests.TestNotApplicable(
1296
'pycurl does not check HTTP_PROXY for security reasons')
1297
self.overrideEnv('HTTP_PROXY', self.proxy_url)
1298
self.assertProxied()
847
self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
1300
849
def test_all_proxy(self):
1301
self.overrideEnv('all_proxy', self.proxy_url)
1302
self.assertProxied()
850
self.proxied_in_env({'all_proxy': self.proxy_url})
1304
852
def test_ALL_PROXY(self):
1305
self.overrideEnv('ALL_PROXY', self.proxy_url)
1306
self.assertProxied()
853
self.proxied_in_env({'ALL_PROXY': self.proxy_url})
1308
855
def test_http_proxy_with_no_proxy(self):
1309
self.overrideEnv('no_proxy', self.no_proxy_host)
1310
self.overrideEnv('http_proxy', self.proxy_url)
1311
self.assertNotProxied()
856
self.not_proxied_in_env({'http_proxy': self.proxy_url,
857
'no_proxy': self.no_proxy_host})
1313
859
def test_HTTP_PROXY_with_NO_PROXY(self):
1314
if self._testing_pycurl():
1315
raise tests.TestNotApplicable(
1316
'pycurl does not check HTTP_PROXY for security reasons')
1317
self.overrideEnv('NO_PROXY', self.no_proxy_host)
1318
self.overrideEnv('HTTP_PROXY', self.proxy_url)
1319
self.assertNotProxied()
860
self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
861
'NO_PROXY': self.no_proxy_host})
1321
863
def test_all_proxy_with_no_proxy(self):
1322
self.overrideEnv('no_proxy', self.no_proxy_host)
1323
self.overrideEnv('all_proxy', self.proxy_url)
1324
self.assertNotProxied()
864
self.not_proxied_in_env({'all_proxy': self.proxy_url,
865
'no_proxy': self.no_proxy_host})
1326
867
def test_ALL_PROXY_with_NO_PROXY(self):
1327
self.overrideEnv('NO_PROXY', self.no_proxy_host)
1328
self.overrideEnv('ALL_PROXY', self.proxy_url)
1329
self.assertNotProxied()
1331
def test_http_proxy_without_scheme(self):
1332
self.overrideEnv('http_proxy', self.server_host_port)
1333
if self._testing_pycurl():
1334
# pycurl *ignores* invalid proxy env variables. If that ever change
1335
# in the future, this test will fail indicating that pycurl do not
1336
# ignore anymore such variables.
1337
self.assertNotProxied()
1339
self.assertRaises(errors.InvalidURL, self.assertProxied)
1342
class TestRanges(http_utils.TestCaseWithWebserver):
1343
"""Test the Range header in GET methods."""
1345
scenarios = multiply_scenarios(
1346
vary_by_http_client_implementation(),
1347
vary_by_http_protocol_version(),
1351
http_utils.TestCaseWithWebserver.setUp(self)
868
self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
869
'NO_PROXY': self.no_proxy_host})
871
def test_http_proxy_without_scheme(self):
872
self.assertRaises(errors.InvalidURL,
874
{'http_proxy': self.proxy_address})
877
class TestProxyHttpServer_urllib(TestProxyHttpServer,
878
TestCaseWithTwoWebservers):
879
"""Tests proxy server for urllib implementation"""
881
_transport = HttpTransport_urllib
884
class TestProxyHttpServer_pycurl(TestWithTransport_pycurl,
886
TestCaseWithTwoWebservers):
887
"""Tests proxy server for pycurl implementation"""
890
TestProxyHttpServer.setUp(self)
891
# Oh my ! pycurl does not check for the port as part of
892
# no_proxy :-( So we just test the host part
893
self.no_proxy_host = 'localhost'
895
def test_HTTP_PROXY(self):
896
# pycurl do not check HTTP_PROXY for security reasons
897
# (for use in a CGI context that we do not care
898
# about. Should we ?)
901
def test_HTTP_PROXY_with_NO_PROXY(self):
904
def test_http_proxy_without_scheme(self):
905
# pycurl *ignores* invalid proxy env variables. If that
906
# ever change in the future, this test will fail
907
# indicating that pycurl do not ignore anymore such
909
self.not_proxied_in_env({'http_proxy': self.proxy_address})
912
class TestRanges(object):
913
"""Test the Range header in GET methods..
915
This MUST be used by daughter classes that also inherit from
916
TestCaseWithWebserver.
918
We can't inherit directly from TestCaseWithWebserver or the
919
test framework will try to create an instance which cannot
920
run, its implementation being incomplete.
924
TestCaseWithWebserver.setUp(self)
1352
925
self.build_tree_contents([('a', '0123456789')],)
1354
def create_transport_readonly_server(self):
1355
return http_server.HttpServer(protocol_version=self._protocol_version)
1357
def _file_contents(self, relpath, ranges):
1358
t = self.get_readonly_transport()
1359
offsets = [ (start, end - start + 1) for start, end in ranges]
1360
coalesce = t._coalesce_offsets
1361
coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
1362
code, data = t._get(relpath, coalesced)
1363
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1364
for start, end in ranges:
1366
yield data.read(end - start + 1)
926
server = self.get_readonly_server()
927
self.transport = self._transport(server.get_url())
929
def _file_contents(self, relpath, ranges, tail_amount=0):
930
code, data = self.transport._get(relpath, ranges)
931
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
932
for start, end in ranges:
934
yield data.read(end - start + 1)
1368
936
def _file_tail(self, relpath, tail_amount):
1369
t = self.get_readonly_transport()
1370
code, data = t._get(relpath, [], tail_amount)
1371
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1372
data.seek(-tail_amount, 2)
1373
return data.read(tail_amount)
937
code, data = self.transport._get(relpath, [], tail_amount)
938
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
939
data.seek(-tail_amount + 1, 2)
940
return data.read(tail_amount)
1375
942
def test_range_header(self):
1377
944
map(self.assertEqual,['0', '234'],
1378
945
list(self._file_contents('a', [(0,0), (2,4)])),)
1380
def test_range_header_tail(self):
1381
947
self.assertEqual('789', self._file_tail('a', 3))
1383
def test_syntactically_invalid_range_header(self):
1384
self.assertListRaises(errors.InvalidHttpRange,
1385
self._file_contents, 'a', [(4, 3)])
1387
def test_semantically_invalid_range_header(self):
1388
self.assertListRaises(errors.InvalidHttpRange,
1389
self._file_contents, 'a', [(42, 128)])
1392
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1393
"""Test redirection between http servers."""
1395
scenarios = multiply_scenarios(
1396
vary_by_http_client_implementation(),
1397
vary_by_http_protocol_version(),
948
# Syntactically invalid range
949
self.assertRaises(errors.InvalidRange,
950
self.transport._get, 'a', [(4, 3)])
951
# Semantically invalid range
952
self.assertRaises(errors.InvalidRange,
953
self.transport._get, 'a', [(42, 128)])
956
class TestRanges_urllib(TestRanges, TestCaseWithWebserver):
957
"""Test the Range header in GET methods for urllib implementation"""
959
_transport = HttpTransport_urllib
962
class TestRanges_pycurl(TestWithTransport_pycurl,
964
TestCaseWithWebserver):
965
"""Test the Range header in GET methods for pycurl implementation"""
968
class TestHTTPRedirections(object):
969
"""Test redirection between http servers.
971
This MUST be used by daughter classes that also inherit from
972
TestCaseWithRedirectedWebserver.
974
We can't inherit directly from TestCaseWithTwoWebservers or the
975
test framework will try to create an instance which cannot
976
run, its implementation being incomplete.
979
def create_transport_secondary_server(self):
980
"""Create the secondary server redirecting to the primary server"""
981
new = self.get_readonly_server()
983
redirecting = HTTPServerRedirecting()
984
redirecting.redirect_to(new.host, new.port)
1400
987
def setUp(self):
1401
988
super(TestHTTPRedirections, self).setUp()
1847
1287
('b-proxied', 'contents of b\n'),
1850
def get_user_transport(self, user, password):
1851
self.overrideEnv('all_proxy', self.get_user_url(user, password))
1852
return TestAuth.get_user_transport(self, user, password)
1854
def test_empty_pass(self):
1855
if self._testing_pycurl():
1857
if pycurl.version_info()[1] < '7.16.0':
1859
'pycurl < 7.16.0 does not handle empty proxy passwords')
1860
super(TestProxyAuth, self).test_empty_pass()
1863
class SampleSocket(object):
1864
"""A socket-like object for use in testing the HTTP request handler."""
1866
def __init__(self, socket_read_content):
1867
"""Constructs a sample socket.
1869
:param socket_read_content: a byte sequence
1871
# Use plain python StringIO so we can monkey-patch the close method to
1872
# not discard the contents.
1873
from StringIO import StringIO
1874
self.readfile = StringIO(socket_read_content)
1875
self.writefile = StringIO()
1876
self.writefile.close = lambda: None
1877
self.close = lambda: None
1879
def makefile(self, mode='r', bufsize=None):
1881
return self.readfile
1883
return self.writefile
1886
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1888
scenarios = multiply_scenarios(
1889
vary_by_http_client_implementation(),
1890
vary_by_http_protocol_version(),
1894
super(SmartHTTPTunnellingTest, self).setUp()
1895
# We use the VFS layer as part of HTTP tunnelling tests.
1896
self.overrideEnv('BZR_NO_SMART_VFS', None)
1897
self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1898
self.http_server = self.get_readonly_server()
1900
def create_transport_readonly_server(self):
1901
server = http_utils.HTTPServerWithSmarts(
1902
protocol_version=self._protocol_version)
1903
server._url_protocol = self._url_protocol
1906
def test_open_bzrdir(self):
1907
branch = self.make_branch('relpath')
1908
url = self.http_server.get_url() + 'relpath'
1909
bd = bzrdir.BzrDir.open(url)
1910
self.addCleanup(bd.transport.disconnect)
1911
self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1913
def test_bulk_data(self):
1914
# We should be able to send and receive bulk data in a single message.
1915
# The 'readv' command in the smart protocol both sends and receives
1916
# bulk data, so we use that.
1917
self.build_tree(['data-file'])
1918
http_transport = transport.get_transport(self.http_server.get_url())
1919
medium = http_transport.get_smart_medium()
1920
# Since we provide the medium, the url below will be mostly ignored
1921
# during the test, as long as the path is '/'.
1922
remote_transport = remote.RemoteTransport('bzr://fake_host/',
1925
[(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
1927
def test_http_send_smart_request(self):
1929
post_body = 'hello\n'
1930
expected_reply_body = 'ok\x012\n'
1932
http_transport = transport.get_transport(self.http_server.get_url())
1933
medium = http_transport.get_smart_medium()
1934
response = medium.send_http_smart_request(post_body)
1935
reply_body = response.read()
1936
self.assertEqual(expected_reply_body, reply_body)
1938
def test_smart_http_server_post_request_handler(self):
1939
httpd = self.http_server.server
1941
socket = SampleSocket(
1942
'POST /.bzr/smart %s \r\n' % self._protocol_version
1943
# HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
1945
+ 'Content-Length: 6\r\n'
1948
# Beware: the ('localhost', 80) below is the
1949
# client_address parameter, but we don't have one because
1950
# we have defined a socket which is not bound to an
1951
# address. The test framework never uses this client
1952
# address, so far...
1953
request_handler = http_utils.SmartRequestHandler(socket,
1956
response = socket.writefile.getvalue()
1957
self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
1958
# This includes the end of the HTTP headers, and all the body.
1959
expected_end_of_response = '\r\n\r\nok\x012\n'
1960
self.assertEndsWith(response, expected_end_of_response)
1963
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
1964
"""No smart server here request handler."""
1967
self.send_error(403, "Forbidden")
1970
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
1971
"""Test smart client behaviour against an http server without smarts."""
1973
_req_handler_class = ForbiddenRequestHandler
1975
def test_probe_smart_server(self):
1976
"""Test error handling against server refusing smart requests."""
1977
t = self.get_readonly_transport()
1978
# No need to build a valid smart request here, the server will not even
1979
# try to interpret it.
1980
self.assertRaises(errors.SmartProtocolError,
1981
t.get_smart_medium().send_http_smart_request,
1985
class Test_redirected_to(tests.TestCase):
1987
scenarios = vary_by_http_client_implementation()
1989
def test_redirected_to_subdir(self):
1990
t = self._transport('http://www.example.com/foo')
1991
r = t._redirected_to('http://www.example.com/foo',
1992
'http://www.example.com/foo/subdir')
1993
self.assertIsInstance(r, type(t))
1994
# Both transports share the some connection
1995
self.assertEqual(t._get_connection(), r._get_connection())
1997
def test_redirected_to_self_with_slash(self):
1998
t = self._transport('http://www.example.com/foo')
1999
r = t._redirected_to('http://www.example.com/foo',
2000
'http://www.example.com/foo/')
2001
self.assertIsInstance(r, type(t))
2002
# Both transports share the some connection (one can argue that we
2003
# should return the exact same transport here, but that seems
2005
self.assertEqual(t._get_connection(), r._get_connection())
2007
def test_redirected_to_host(self):
2008
t = self._transport('http://www.example.com/foo')
2009
r = t._redirected_to('http://www.example.com/foo',
2010
'http://foo.example.com/foo/subdir')
2011
self.assertIsInstance(r, type(t))
2013
def test_redirected_to_same_host_sibling_protocol(self):
2014
t = self._transport('http://www.example.com/foo')
2015
r = t._redirected_to('http://www.example.com/foo',
2016
'https://www.example.com/foo')
2017
self.assertIsInstance(r, type(t))
2019
def test_redirected_to_same_host_different_protocol(self):
2020
t = self._transport('http://www.example.com/foo')
2021
r = t._redirected_to('http://www.example.com/foo',
2022
'ftp://www.example.com/foo')
2023
self.assertNotEquals(type(r), type(t))
2025
def test_redirected_to_different_host_same_user(self):
2026
t = self._transport('http://joe@www.example.com/foo')
2027
r = t._redirected_to('http://www.example.com/foo',
2028
'https://foo.example.com/foo')
2029
self.assertIsInstance(r, type(t))
2030
self.assertEqual(t._parsed_url.user, r._parsed_url.user)
2033
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
2034
"""Request handler for a unique and pre-defined request.
2036
The only thing we care about here is how many bytes travel on the wire. But
2037
since we want to measure it for a real http client, we have to send it
2040
We expect to receive a *single* request nothing more (and we won't even
2041
check what request it is, we just measure the bytes read until an empty
2045
def _handle_one_request(self):
2046
tcs = self.server.test_case_server
2047
requestline = self.rfile.readline()
2048
headers = self.MessageClass(self.rfile, 0)
2049
# We just read: the request, the headers, an empty line indicating the
2050
# end of the headers.
2051
bytes_read = len(requestline)
2052
for line in headers.headers:
2053
bytes_read += len(line)
2054
bytes_read += len('\r\n')
2055
if requestline.startswith('POST'):
2056
# The body should be a single line (or we don't know where it ends
2057
# and we don't want to issue a blocking read)
2058
body = self.rfile.readline()
2059
bytes_read += len(body)
2060
tcs.bytes_read = bytes_read
2062
# We set the bytes written *before* issuing the write, the client is
2063
# supposed to consume every produced byte *before* checking that value.
2065
# Doing the oppposite may lead to test failure: we may be interrupted
2066
# after the write but before updating the value. The client can then
2067
# continue and read the value *before* we can update it. And yes,
2068
# this has been observed -- vila 20090129
2069
tcs.bytes_written = len(tcs.canned_response)
2070
self.wfile.write(tcs.canned_response)
2073
class ActivityServerMixin(object):
2075
def __init__(self, protocol_version):
2076
super(ActivityServerMixin, self).__init__(
2077
request_handler=PredefinedRequestHandler,
2078
protocol_version=protocol_version)
2079
# Bytes read and written by the server
2081
self.bytes_written = 0
2082
self.canned_response = None
2085
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
2089
if features.HTTPSServerFeature.available():
2090
from bzrlib.tests import https_server
2091
class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
2095
class TestActivityMixin(object):
2096
"""Test socket activity reporting.
2098
We use a special purpose server to control the bytes sent and received and
2099
be able to predict the activity on the client socket.
2103
tests.TestCase.setUp(self)
2104
self.server = self._activity_server(self._protocol_version)
2105
self.server.start_server()
2106
_activities = {} # Don't close over self and create a cycle
2107
def report_activity(t, bytes, direction):
2108
count = _activities.get(direction, 0)
2110
_activities[direction] = count
2111
self.activities = _activities
2113
# We override at class level because constructors may propagate the
2114
# bound method and render instance overriding ineffective (an
2115
# alternative would be to define a specific ui factory instead...)
2116
self.overrideAttr(self._transport, '_report_activity', report_activity)
2117
self.addCleanup(self.server.stop_server)
2119
def get_transport(self):
2120
t = self._transport(self.server.get_url())
2121
# FIXME: Needs cleanup -- vila 20100611
2124
def assertActivitiesMatch(self):
2125
self.assertEqual(self.server.bytes_read,
2126
self.activities.get('write', 0), 'written bytes')
2127
self.assertEqual(self.server.bytes_written,
2128
self.activities.get('read', 0), 'read bytes')
2131
self.server.canned_response = '''HTTP/1.1 200 OK\r
2132
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2133
Server: Apache/2.0.54 (Fedora)\r
2134
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2135
ETag: "56691-23-38e9ae00"\r
2136
Accept-Ranges: bytes\r
2137
Content-Length: 35\r
2139
Content-Type: text/plain; charset=UTF-8\r
2141
Bazaar-NG meta directory, format 1
2143
t = self.get_transport()
2144
self.assertEqual('Bazaar-NG meta directory, format 1\n',
2145
t.get('foo/bar').read())
2146
self.assertActivitiesMatch()
2149
self.server.canned_response = '''HTTP/1.1 200 OK\r
2150
Server: SimpleHTTP/0.6 Python/2.5.2\r
2151
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
2152
Content-type: application/octet-stream\r
2153
Content-Length: 20\r
2154
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
2157
t = self.get_transport()
2158
self.assertTrue(t.has('foo/bar'))
2159
self.assertActivitiesMatch()
2161
def test_readv(self):
2162
self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
2163
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
2164
Server: Apache/2.0.54 (Fedora)\r
2165
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
2166
ETag: "238a3c-16ec2-805c5540"\r
2167
Accept-Ranges: bytes\r
2168
Content-Length: 1534\r
2170
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
2173
--418470f848b63279b\r
2174
Content-type: text/plain; charset=UTF-8\r
2175
Content-range: bytes 0-254/93890\r
2177
mbp@sourcefrog.net-20050309040815-13242001617e4a06
2178
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
2179
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
2180
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
2181
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
2183
--418470f848b63279b\r
2184
Content-type: text/plain; charset=UTF-8\r
2185
Content-range: bytes 1000-2049/93890\r
2188
mbp@sourcefrog.net-20050311063625-07858525021f270b
2189
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
2190
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
2191
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
2192
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
2193
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
2194
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
2195
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
2196
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
2197
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
2198
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
2199
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
2200
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
2201
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
2202
mbp@sourcefrog.net-20050313120651-497bd231b19df600
2203
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
2204
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
2205
mbp@sourcefrog.net-20050314025539-637a636692c055cf
2206
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
2207
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
2209
--418470f848b63279b--\r
2211
t = self.get_transport()
2212
# Remember that the request is ignored and that the ranges below
2213
# doesn't have to match the canned response.
2214
l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
2215
self.assertEqual(2, len(l))
2216
self.assertActivitiesMatch()
2218
def test_post(self):
2219
self.server.canned_response = '''HTTP/1.1 200 OK\r
2220
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2221
Server: Apache/2.0.54 (Fedora)\r
2222
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2223
ETag: "56691-23-38e9ae00"\r
2224
Accept-Ranges: bytes\r
2225
Content-Length: 35\r
2227
Content-Type: text/plain; charset=UTF-8\r
2229
lalala whatever as long as itsssss
2231
t = self.get_transport()
2232
# We must send a single line of body bytes, see
2233
# PredefinedRequestHandler._handle_one_request
2234
code, f = t._post('abc def end-of-body\n')
2235
self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2236
self.assertActivitiesMatch()
2239
class TestActivity(tests.TestCase, TestActivityMixin):
2241
scenarios = multiply_scenarios(
2242
vary_by_http_activity(),
2243
vary_by_http_protocol_version(),
2247
TestActivityMixin.setUp(self)
2250
class TestNoReportActivity(tests.TestCase, TestActivityMixin):
2252
# Unlike TestActivity, we are really testing ReportingFileSocket and
2253
# ReportingSocket, so we don't need all the parametrization. Since
2254
# ReportingFileSocket and ReportingSocket are wrappers, it's easier to
2255
# test them through their use by the transport than directly (that's a
2256
# bit less clean but far more simpler and effective).
2257
_activity_server = ActivityHTTPServer
2258
_protocol_version = 'HTTP/1.1'
2261
self._transport =_urllib.HttpTransport_urllib
2262
TestActivityMixin.setUp(self)
2264
def assertActivitiesMatch(self):
2265
# Nothing to check here
2269
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
2270
"""Test authentication on the redirected http server."""
2272
scenarios = vary_by_http_protocol_version()
2274
_auth_header = 'Authorization'
2275
_password_prompt_prefix = ''
2276
_username_prompt_prefix = ''
2277
_auth_server = http_utils.HTTPBasicAuthServer
2278
_transport = _urllib.HttpTransport_urllib
2281
super(TestAuthOnRedirected, self).setUp()
2282
self.build_tree_contents([('a','a'),
2284
('1/a', 'redirected once'),
2286
new_prefix = 'http://%s:%s' % (self.new_server.host,
2287
self.new_server.port)
2288
self.old_server.redirections = [
2289
('(.*)', r'%s/1\1' % (new_prefix), 301),]
2290
self.old_transport = self.get_old_transport()
2291
self.new_server.add_user('joe', 'foo')
2292
cleanup_http_redirection_connections(self)
2294
def create_transport_readonly_server(self):
2295
server = self._auth_server(protocol_version=self._protocol_version)
2296
server._url_protocol = self._url_protocol
2302
def test_auth_on_redirected_via_do_catching_redirections(self):
2303
self.redirections = 0
2305
def redirected(t, exception, redirection_notice):
2306
self.redirections += 1
2307
redirected_t = t._redirected_to(exception.source, exception.target)
2308
self.addCleanup(redirected_t.disconnect)
2311
stdout = tests.StringIOWrapper()
2312
stderr = tests.StringIOWrapper()
2313
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
2314
stdout=stdout, stderr=stderr)
2315
self.assertEqual('redirected once',
2316
transport.do_catching_redirections(
2317
self.get_a, self.old_transport, redirected).read())
2318
self.assertEqual(1, self.redirections)
2319
# stdin should be empty
2320
self.assertEqual('', ui.ui_factory.stdin.readline())
2321
# stdout should be empty, stderr will contains the prompts
2322
self.assertEqual('', stdout.getvalue())
2324
def test_auth_on_redirected_via_following_redirections(self):
2325
self.new_server.add_user('joe', 'foo')
2326
stdout = tests.StringIOWrapper()
2327
stderr = tests.StringIOWrapper()
2328
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
2329
stdout=stdout, stderr=stderr)
2330
t = self.old_transport
2331
req = RedirectedRequest('GET', t.abspath('a'))
2332
new_prefix = 'http://%s:%s' % (self.new_server.host,
2333
self.new_server.port)
2334
self.old_server.redirections = [
2335
('(.*)', r'%s/1\1' % (new_prefix), 301),]
2336
self.assertEqual('redirected once', t._perform(req).read())
2337
# stdin should be empty
2338
self.assertEqual('', ui.ui_factory.stdin.readline())
2339
# stdout should be empty, stderr will contains the prompts
2340
self.assertEqual('', stdout.getvalue())
1290
def get_user_transport(self, user=None, password=None):
1291
self._install_env({'all_proxy': self.get_user_url(user, password)})
1292
return self._transport(self.server.get_url())
1294
def _install_env(self, env):
1295
for name, value in env.iteritems():
1296
self._old_env[name] = osutils.set_or_unset_env(name, value)
1298
def _restore_env(self):
1299
for name, value in self._old_env.iteritems():
1300
osutils.set_or_unset_env(name, value)
1303
class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
1304
"""Test http basic authentication scheme"""
1306
_transport = HttpTransport_urllib
1308
def create_transport_readonly_server(self):
1309
return HTTPBasicAuthServer()
1312
class TestHTTPProxyBasicAuth(TestProxyAuth, TestCaseWithWebserver):
1313
"""Test proxy basic authentication scheme"""
1315
_transport = HttpTransport_urllib
1317
def create_transport_readonly_server(self):
1318
return ProxyBasicAuthServer()
1321
class TestDigestAuth(object):
1322
"""Digest Authentication specific tests"""
1324
def test_changing_nonce(self):
1325
self.server.add_user('joe', 'foo')
1326
t = self.get_user_transport('joe', 'foo')
1327
self.assertEqual('contents of a\n', t.get('a').read())
1328
self.assertEqual('contents of b\n', t.get('b').read())
1329
# Only one 'Authentication Required' error should have
1331
self.assertEqual(1, self.server.auth_required_errors)
1332
# The server invalidates the current nonce
1333
self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1334
self.assertEqual('contents of a\n', t.get('a').read())
1335
# Two 'Authentication Required' errors should occur (the
1336
# initial 'who are you' and a second 'who are you' with the new nonce)
1337
self.assertEqual(2, self.server.auth_required_errors)
1340
class TestHTTPDigestAuth(TestHTTPAuth, TestDigestAuth, TestCaseWithWebserver):
1341
"""Test http digest authentication scheme"""
1343
_transport = HttpTransport_urllib
1345
def create_transport_readonly_server(self):
1346
return HTTPDigestAuthServer()
1349
class TestHTTPProxyDigestAuth(TestProxyAuth, TestDigestAuth,
1350
TestCaseWithWebserver):
1351
"""Test proxy digest authentication scheme"""
1353
_transport = HttpTransport_urllib
1355
def create_transport_readonly_server(self):
1356
return ProxyDigestAuthServer()