95
171
self.received_bytes = ''
175
return '%s://%s:%s/' % (self.scheme, self.host, self.port)
177
def start_server(self):
98
178
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
99
179
self._sock.bind(('127.0.0.1', 0))
100
180
self.host, self.port = self._sock.getsockname()
101
181
self._ready = threading.Event()
102
self._thread = threading.Thread(target=self._accept_read_and_reply)
103
self._thread.setDaemon(True)
182
self._thread = test_server.TestThread(
183
sync_event=self._ready, target=self._accept_read_and_reply)
104
184
self._thread.start()
185
if 'threads' in tests.selftest_debug_flags:
186
sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
107
189
def _accept_read_and_reply(self):
108
190
self._sock.listen(1)
109
191
self._ready.set()
110
self._sock.settimeout(5)
112
conn, address = self._sock.accept()
113
# On win32, the accepted connection will be non-blocking to start
114
# with because we're using settimeout.
115
conn.setblocking(True)
192
conn, address = self._sock.accept()
193
if self._expect_body_tail is not None:
116
194
while not self.received_bytes.endswith(self._expect_body_tail):
117
195
self.received_bytes += conn.recv(4096)
118
196
conn.sendall('HTTP/1.1 200 OK\r\n')
119
except socket.timeout:
120
# Make sure the client isn't stuck waiting for us to e.g. accept.
121
198
self._sock.close()
122
199
except socket.error:
123
200
# The client may have already closed the socket.
203
def stop_server(self):
205
# Issue a fake connection to wake up the server and allow it to
207
fake_conn = osutils.connect_socket((self.host, self.port))
129
209
except socket.error:
130
210
# We might have already closed it. We don't care.
215
if 'threads' in tests.selftest_debug_flags:
216
sys.stderr.write('Thread joined: %s\n' % (self._thread.ident,))
219
class TestAuthHeader(tests.TestCase):
221
def parse_header(self, header, auth_handler_class=None):
222
if auth_handler_class is None:
223
auth_handler_class = _urllib2_wrappers.AbstractAuthHandler
224
self.auth_handler = auth_handler_class()
225
return self.auth_handler._parse_auth_header(header)
227
def test_empty_header(self):
228
scheme, remainder = self.parse_header('')
229
self.assertEqual('', scheme)
230
self.assertIs(None, remainder)
232
def test_negotiate_header(self):
233
scheme, remainder = self.parse_header('Negotiate')
234
self.assertEqual('negotiate', scheme)
235
self.assertIs(None, remainder)
237
def test_basic_header(self):
238
scheme, remainder = self.parse_header(
239
'Basic realm="Thou should not pass"')
240
self.assertEqual('basic', scheme)
241
self.assertEqual('realm="Thou should not pass"', remainder)
243
def test_basic_extract_realm(self):
244
scheme, remainder = self.parse_header(
245
'Basic realm="Thou should not pass"',
246
_urllib2_wrappers.BasicAuthHandler)
247
match, realm = self.auth_handler.extract_realm(remainder)
248
self.assertTrue(match is not None)
249
self.assertEqual('Thou should not pass', realm)
251
def test_digest_header(self):
252
scheme, remainder = self.parse_header(
253
'Digest realm="Thou should not pass"')
254
self.assertEqual('digest', scheme)
255
self.assertEqual('realm="Thou should not pass"', remainder)
258
class TestHTTPRangeParsing(tests.TestCase):
261
super(TestHTTPRangeParsing, self).setUp()
262
# We focus on range parsing here and ignore everything else
263
class RequestHandler(http_server.TestingHTTPRequestHandler):
264
def setup(self): pass
265
def handle(self): pass
266
def finish(self): pass
268
self.req_handler = RequestHandler(None, None, None)
270
def assertRanges(self, ranges, header, file_size):
271
self.assertEquals(ranges,
272
self.req_handler._parse_ranges(header, file_size))
274
def test_simple_range(self):
275
self.assertRanges([(0,2)], 'bytes=0-2', 12)
278
self.assertRanges([(8, 11)], 'bytes=-4', 12)
280
def test_tail_bigger_than_file(self):
281
self.assertRanges([(0, 11)], 'bytes=-99', 12)
283
def test_range_without_end(self):
284
self.assertRanges([(4, 11)], 'bytes=4-', 12)
286
def test_invalid_ranges(self):
287
self.assertRanges(None, 'bytes=12-22', 12)
288
self.assertRanges(None, 'bytes=1-3,12-22', 12)
289
self.assertRanges(None, 'bytes=-', 12)
292
class TestHTTPServer(tests.TestCase):
293
"""Test the HTTP servers implementations."""
295
def test_invalid_protocol(self):
296
class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
298
protocol_version = 'HTTP/0.1'
300
self.assertRaises(httplib.UnknownProtocol,
301
http_server.HttpServer, BogusRequestHandler)
303
def test_force_invalid_protocol(self):
304
self.assertRaises(httplib.UnknownProtocol,
305
http_server.HttpServer, protocol_version='HTTP/0.1')
307
def test_server_start_and_stop(self):
308
server = http_server.HttpServer()
309
self.addCleanup(server.stop_server)
310
server.start_server()
311
self.assertTrue(server.server is not None)
312
self.assertTrue(server.server.serving is not None)
313
self.assertTrue(server.server.serving)
315
def test_create_http_server_one_zero(self):
316
class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
318
protocol_version = 'HTTP/1.0'
320
server = http_server.HttpServer(RequestHandlerOneZero)
321
self.start_server(server)
322
self.assertIsInstance(server.server, http_server.TestingHTTPServer)
324
def test_create_http_server_one_one(self):
325
class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
327
protocol_version = 'HTTP/1.1'
329
server = http_server.HttpServer(RequestHandlerOneOne)
330
self.start_server(server)
331
self.assertIsInstance(server.server,
332
http_server.TestingThreadingHTTPServer)
334
def test_create_http_server_force_one_one(self):
335
class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
337
protocol_version = 'HTTP/1.0'
339
server = http_server.HttpServer(RequestHandlerOneZero,
340
protocol_version='HTTP/1.1')
341
self.start_server(server)
342
self.assertIsInstance(server.server,
343
http_server.TestingThreadingHTTPServer)
345
def test_create_http_server_force_one_zero(self):
346
class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
348
protocol_version = 'HTTP/1.1'
350
server = http_server.HttpServer(RequestHandlerOneOne,
351
protocol_version='HTTP/1.0')
352
self.start_server(server)
353
self.assertIsInstance(server.server,
354
http_server.TestingHTTPServer)
136
357
class TestWithTransport_pycurl(object):
137
358
"""Test case to inherit from if pycurl is present"""
139
360
def _get_pycurl_maybe(self):
141
from bzrlib.transport.http._pycurl import PyCurlTransport
142
return PyCurlTransport
143
except errors.DependencyNotPresent:
144
raise TestSkipped('pycurl not present')
361
self.requireFeature(features.pycurl)
362
return PyCurlTransport
146
364
_transport = property(_get_pycurl_maybe)
149
class TestHttpUrls(TestCase):
367
class TestHttpUrls(tests.TestCase):
151
369
# TODO: This should be moved to authorization tests once they
154
372
def test_url_parsing(self):
155
373
f = FakeManager()
156
url = extract_auth('http://example.com', f)
157
self.assertEquals('http://example.com', url)
158
self.assertEquals(0, len(f.credentials))
159
url = extract_auth('http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
160
self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
161
self.assertEquals(1, len(f.credentials))
162
self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
166
class TestHttpTransportUrls(object):
167
"""Test the http urls.
169
This MUST be used by daughter classes that also inherit from
172
We can't inherit directly from TestCase or the
173
test framework will try to create an instance which cannot
174
run, its implementation being incomplete.
374
url = http.extract_auth('http://example.com', f)
375
self.assertEqual('http://example.com', url)
376
self.assertEqual(0, len(f.credentials))
377
url = http.extract_auth(
378
'http://user:pass@example.com/bzr/bzr.dev', f)
379
self.assertEqual('http://example.com/bzr/bzr.dev', url)
380
self.assertEqual(1, len(f.credentials))
381
self.assertEqual([None, 'example.com', 'user', 'pass'],
385
class TestHttpTransportUrls(tests.TestCase):
386
"""Test the http urls."""
388
scenarios = vary_by_http_client_implementation()
177
390
def test_abs_url(self):
178
391
"""Construction of absolute http URLs"""
179
t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
392
t = self._transport('http://example.com/bzr/bzr.dev/')
180
393
eq = self.assertEqualDiff
181
eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
182
eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
183
eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
394
eq(t.abspath('.'), 'http://example.com/bzr/bzr.dev')
395
eq(t.abspath('foo/bar'), 'http://example.com/bzr/bzr.dev/foo/bar')
396
eq(t.abspath('.bzr'), 'http://example.com/bzr/bzr.dev/.bzr')
184
397
eq(t.abspath('.bzr/1//2/./3'),
185
'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
398
'http://example.com/bzr/bzr.dev/.bzr/1/2/3')
187
400
def test_invalid_http_urls(self):
188
401
"""Trap invalid construction of urls"""
189
t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
190
self.assertRaises(ValueError, t.abspath, '.bzr/')
191
t = self._transport('http://http://bazaar-vcs.org/bzr/bzr.dev/')
192
self.assertRaises((errors.InvalidURL, errors.ConnectionError),
402
self._transport('http://example.com/bzr/bzr.dev/')
403
self.assertRaises(errors.InvalidURL,
405
'http://http://example.com/bzr/bzr.dev/')
195
407
def test_http_root_urls(self):
196
408
"""Construction of URLs from server root"""
197
t = self._transport('http://bzr.ozlabs.org/')
409
t = self._transport('http://example.com/')
198
410
eq = self.assertEqualDiff
199
411
eq(t.abspath('.bzr/tree-version'),
200
'http://bzr.ozlabs.org/.bzr/tree-version')
412
'http://example.com/.bzr/tree-version')
202
414
def test_http_impl_urls(self):
203
415
"""There are servers which ask for particular clients to connect"""
204
416
server = self._server()
417
server.start_server()
207
419
url = server.get_url()
208
self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
420
self.assertTrue(url.startswith('%s://' % self._url_protocol))
213
class TestHttpUrls_urllib(TestHttpTransportUrls, TestCase):
214
"""Test http urls with urllib"""
216
_transport = HttpTransport_urllib
217
_server = HttpServer_urllib
218
_qualified_prefix = 'http+urllib'
221
class TestHttpUrls_pycurl(TestWithTransport_pycurl, TestHttpTransportUrls,
223
"""Test http urls with pycurl"""
225
_server = HttpServer_PyCurl
226
_qualified_prefix = 'http+pycurl'
425
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
228
427
# TODO: This should really be moved into another pycurl
229
428
# specific test. When https tests will be implemented, take
658
812
self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
659
813
t.readv, 'a', [(12,2)])
815
def test_readv_multiple_get_requests(self):
816
server = self.get_readonly_server()
817
t = self.get_readonly_transport()
818
# force transport to issue multiple requests
819
t._max_readv_combine = 1
820
t._max_get_ranges = 1
821
l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
822
self.assertEqual(l[0], (0, '0'))
823
self.assertEqual(l[1], (1, '1'))
824
self.assertEqual(l[2], (3, '34'))
825
self.assertEqual(l[3], (9, '9'))
826
# The server should have issued 4 requests
827
self.assertEqual(4, server.GET_request_nb)
829
def test_readv_get_max_size(self):
830
server = self.get_readonly_server()
831
t = self.get_readonly_transport()
832
# force transport to issue multiple requests by limiting the number of
833
# bytes by request. Note that this apply to coalesced offsets only, a
834
# single range will keep its size even if bigger than the limit.
836
l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
837
self.assertEqual(l[0], (0, '0'))
838
self.assertEqual(l[1], (1, '1'))
839
self.assertEqual(l[2], (2, '2345'))
840
self.assertEqual(l[3], (6, '6789'))
841
# The server should have issued 3 requests
842
self.assertEqual(3, server.GET_request_nb)
844
def test_complete_readv_leave_pipe_clean(self):
845
server = self.get_readonly_server()
846
t = self.get_readonly_transport()
847
# force transport to issue multiple requests
849
list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
850
# The server should have issued 3 requests
851
self.assertEqual(3, server.GET_request_nb)
852
self.assertEqual('0123456789', t.get_bytes('a'))
853
self.assertEqual(4, server.GET_request_nb)
855
def test_incomplete_readv_leave_pipe_clean(self):
856
server = self.get_readonly_server()
857
t = self.get_readonly_transport()
858
# force transport to issue multiple requests
860
# Don't collapse readv results into a list so that we leave unread
861
# bytes on the socket
862
ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
863
self.assertEqual((0, '0'), ireadv.next())
864
# The server should have issued one request so far
865
self.assertEqual(1, server.GET_request_nb)
866
self.assertEqual('0123456789', t.get_bytes('a'))
867
# get_bytes issued an additional request, the readv pending ones are
869
self.assertEqual(2, server.GET_request_nb)
872
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
873
"""Always reply to range request as if they were single.
875
Don't be explicit about it, just to annoy the clients.
878
def get_multiple_ranges(self, file, file_size, ranges):
879
"""Answer as if it was a single range request and ignores the rest"""
880
(start, end) = ranges[0]
881
return self.get_single_range(file, file_size, start, end)
662
884
class TestSingleRangeRequestServer(TestRangeRequestServer):
663
885
"""Test readv against a server which accept only single range requests"""
665
def create_transport_readonly_server(self):
666
return HttpServer(SingleRangeRequestHandler)
669
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
670
TestCaseWithWebserver):
671
"""Tests single range requests accepting server for urllib implementation"""
673
_transport = HttpTransport_urllib
676
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
677
TestSingleRangeRequestServer,
678
TestCaseWithWebserver):
679
"""Tests single range requests accepting server for pycurl implementation"""
887
_req_handler_class = SingleRangeRequestHandler
890
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
891
"""Only reply to simple range requests, errors out on multiple"""
893
def get_multiple_ranges(self, file, file_size, ranges):
894
"""Refuses the multiple ranges request"""
897
self.send_error(416, "Requested range not satisfiable")
899
(start, end) = ranges[0]
900
return self.get_single_range(file, file_size, start, end)
903
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
904
"""Test readv against a server which only accept single range requests"""
906
_req_handler_class = SingleOnlyRangeRequestHandler
909
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
910
"""Ignore range requests without notice"""
913
# Update the statistics
914
self.server.test_case_server.GET_request_nb += 1
915
# Just bypass the range handling done by TestingHTTPRequestHandler
916
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
682
919
class TestNoRangeRequestServer(TestRangeRequestServer):
683
920
"""Test readv against a server which do not accept range requests"""
922
_req_handler_class = NoRangeRequestHandler
925
class MultipleRangeWithoutContentLengthRequestHandler(
926
http_server.TestingHTTPRequestHandler):
927
"""Reply to multiple range requests without content length header."""
929
def get_multiple_ranges(self, file, file_size, ranges):
930
self.send_response(206)
931
self.send_header('Accept-Ranges', 'bytes')
932
# XXX: this is strange; the 'random' name below seems undefined and
933
# yet the tests pass -- mbp 2010-10-11 bug 658773
934
boundary = "%d" % random.randint(0,0x7FFFFFFF)
935
self.send_header("Content-Type",
936
"multipart/byteranges; boundary=%s" % boundary)
938
for (start, end) in ranges:
939
self.wfile.write("--%s\r\n" % boundary)
940
self.send_header("Content-type", 'application/octet-stream')
941
self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
945
self.send_range_content(file, start, end - start + 1)
947
self.wfile.write("--%s\r\n" % boundary)
950
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
952
_req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
955
class TruncatedMultipleRangeRequestHandler(
956
http_server.TestingHTTPRequestHandler):
957
"""Reply to multiple range requests truncating the last ones.
959
This server generates responses whose Content-Length describes all the
960
ranges, but fail to include the last ones leading to client short reads.
961
This has been observed randomly with lighttpd (bug #179368).
964
_truncated_ranges = 2
966
def get_multiple_ranges(self, file, file_size, ranges):
967
self.send_response(206)
968
self.send_header('Accept-Ranges', 'bytes')
970
self.send_header('Content-Type',
971
'multipart/byteranges; boundary=%s' % boundary)
972
boundary_line = '--%s\r\n' % boundary
973
# Calculate the Content-Length
975
for (start, end) in ranges:
976
content_length += len(boundary_line)
977
content_length += self._header_line_length(
978
'Content-type', 'application/octet-stream')
979
content_length += self._header_line_length(
980
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
981
content_length += len('\r\n') # end headers
982
content_length += end - start # + 1
983
content_length += len(boundary_line)
984
self.send_header('Content-length', content_length)
987
# Send the multipart body
989
for (start, end) in ranges:
990
self.wfile.write(boundary_line)
991
self.send_header('Content-type', 'application/octet-stream')
992
self.send_header('Content-Range', 'bytes %d-%d/%d'
993
% (start, end, file_size))
995
if cur + self._truncated_ranges >= len(ranges):
996
# Abruptly ends the response and close the connection
997
self.close_connection = 1
999
self.send_range_content(file, start, end - start + 1)
1002
self.wfile.write(boundary_line)
1005
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
1007
_req_handler_class = TruncatedMultipleRangeRequestHandler
1010
super(TestTruncatedMultipleRangeServer, self).setUp()
1011
self.build_tree_contents([('a', '0123456789')],)
1013
def test_readv_with_short_reads(self):
1014
server = self.get_readonly_server()
1015
t = self.get_readonly_transport()
1016
# Force separate ranges for each offset
1017
t._bytes_to_read_before_seek = 0
1018
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1019
self.assertEqual((0, '0'), ireadv.next())
1020
self.assertEqual((2, '2'), ireadv.next())
1021
if not self._testing_pycurl():
1022
# Only one request have been issued so far (except for pycurl that
1023
# try to read the whole response at once)
1024
self.assertEqual(1, server.GET_request_nb)
1025
self.assertEqual((4, '45'), ireadv.next())
1026
self.assertEqual((9, '9'), ireadv.next())
1027
# Both implementations issue 3 requests but:
1028
# - urllib does two multiple (4 ranges, then 2 ranges) then a single
1030
# - pycurl does two multiple (4 ranges, 4 ranges) then a single range
1031
self.assertEqual(3, server.GET_request_nb)
1032
# Finally the client have tried a single range request and stays in
1034
self.assertEqual('single', t._range_hint)
1037
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1038
"""Errors out when range specifiers exceed the limit"""
1040
def get_multiple_ranges(self, file, file_size, ranges):
1041
"""Refuses the multiple ranges request"""
1042
tcs = self.server.test_case_server
1043
if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
1045
# Emulate apache behavior
1046
self.send_error(400, "Bad Request")
1048
return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
1049
self, file, file_size, ranges)
1052
class LimitedRangeHTTPServer(http_server.HttpServer):
1053
"""An HttpServer erroring out on requests with too much range specifiers"""
1055
def __init__(self, request_handler=LimitedRangeRequestHandler,
1056
protocol_version=None,
1058
http_server.HttpServer.__init__(self, request_handler,
1059
protocol_version=protocol_version)
1060
self.range_limit = range_limit
1063
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1064
"""Tests readv requests against a server erroring out on too much ranges."""
1066
scenarios = multiply_scenarios(
1067
vary_by_http_client_implementation(),
1068
vary_by_http_protocol_version(),
1071
# Requests with more range specifiers will error out
685
1074
def create_transport_readonly_server(self):
686
return HttpServer(NoRangeRequestHandler)
689
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
690
TestCaseWithWebserver):
691
"""Tests range requests refusing server for urllib implementation"""
693
_transport = HttpTransport_urllib
696
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
697
TestNoRangeRequestServer,
698
TestCaseWithWebserver):
699
"""Tests range requests refusing server for pycurl implementation"""
702
class TestHttpProxyWhiteBox(TestCase):
1075
return LimitedRangeHTTPServer(range_limit=self.range_limit,
1076
protocol_version=self._protocol_version)
1079
http_utils.TestCaseWithWebserver.setUp(self)
1080
# We need to manipulate ranges that correspond to real chunks in the
1081
# response, so we build a content appropriately.
1082
filler = ''.join(['abcdefghij' for x in range(102)])
1083
content = ''.join(['%04d' % v + filler for v in range(16)])
1084
self.build_tree_contents([('a', content)],)
1086
def test_few_ranges(self):
1087
t = self.get_readonly_transport()
1088
l = list(t.readv('a', ((0, 4), (1024, 4), )))
1089
self.assertEqual(l[0], (0, '0000'))
1090
self.assertEqual(l[1], (1024, '0001'))
1091
self.assertEqual(1, self.get_readonly_server().GET_request_nb)
1093
def test_more_ranges(self):
1094
t = self.get_readonly_transport()
1095
l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
1096
self.assertEqual(l[0], (0, '0000'))
1097
self.assertEqual(l[1], (1024, '0001'))
1098
self.assertEqual(l[2], (4096, '0004'))
1099
self.assertEqual(l[3], (8192, '0008'))
1100
# The server will refuse to serve the first request (too much ranges),
1101
# a second request will succeed.
1102
self.assertEqual(2, self.get_readonly_server().GET_request_nb)
1105
class TestHttpProxyWhiteBox(tests.TestCase):
703
1106
"""Whitebox test proxy http authorization.
705
These tests concern urllib implementation only.
1108
Only the urllib implementation is tested here.
715
def _set_and_capture_env_var(self, name, new_value):
716
"""Set an environment variable, and reset it when finished."""
717
self._old_env[name] = osutils.set_or_unset_env(name, new_value)
719
def _install_env(self, env):
720
for name, value in env.iteritems():
721
self._set_and_capture_env_var(name, value)
723
def _restore_env(self):
724
for name, value in self._old_env.iteritems():
725
osutils.set_or_unset_env(name, value)
727
1111
def _proxied_request(self):
728
from bzrlib.transport.http._urllib2_wrappers import (
733
handler = ProxyHandler()
734
request = Request('GET','http://baz/buzzle')
1112
handler = _urllib2_wrappers.ProxyHandler()
1113
request = _urllib2_wrappers.Request('GET', 'http://baz/buzzle')
735
1114
handler.set_proxy(request, 'http')
1117
def assertEvaluateProxyBypass(self, expected, host, no_proxy):
1118
handler = _urllib2_wrappers.ProxyHandler()
1119
self.assertEquals(expected,
1120
handler.evaluate_proxy_bypass(host, no_proxy))
738
1122
def test_empty_user(self):
739
self._install_env({'http_proxy': 'http://bar.com'})
1123
self.overrideEnv('http_proxy', 'http://bar.com')
740
1124
request = self._proxied_request()
741
1125
self.assertFalse(request.headers.has_key('Proxy-authorization'))
743
def test_empty_pass(self):
744
self._install_env({'http_proxy': 'http://joe@bar.com'})
745
request = self._proxied_request()
746
self.assertEqual('Basic ' + 'joe:'.encode('base64').strip(),
747
request.headers['Proxy-authorization'])
748
def test_user_pass(self):
749
self._install_env({'http_proxy': 'http://joe:foo@bar.com'})
750
request = self._proxied_request()
751
self.assertEqual('Basic ' + 'joe:foo'.encode('base64').strip(),
752
request.headers['Proxy-authorization'])
1127
def test_user_with_at(self):
1128
self.overrideEnv('http_proxy',
1129
'http://username@domain:password@proxy_host:1234')
1130
request = self._proxied_request()
1131
self.assertFalse(request.headers.has_key('Proxy-authorization'))
754
1133
def test_invalid_proxy(self):
755
1134
"""A proxy env variable without scheme"""
756
self._install_env({'http_proxy': 'host:1234'})
1135
self.overrideEnv('http_proxy', 'host:1234')
757
1136
self.assertRaises(errors.InvalidURL, self._proxied_request)
760
class TestProxyHttpServer(object):
1138
def test_evaluate_proxy_bypass_true(self):
1139
"""The host is not proxied"""
1140
self.assertEvaluateProxyBypass(True, 'example.com', 'example.com')
1141
self.assertEvaluateProxyBypass(True, 'bzr.example.com', '*example.com')
1143
def test_evaluate_proxy_bypass_false(self):
1144
"""The host is proxied"""
1145
self.assertEvaluateProxyBypass(False, 'bzr.example.com', None)
1147
def test_evaluate_proxy_bypass_unknown(self):
1148
"""The host is not explicitly proxied"""
1149
self.assertEvaluateProxyBypass(None, 'example.com', 'not.example.com')
1150
self.assertEvaluateProxyBypass(None, 'bzr.example.com', 'example.com')
1152
def test_evaluate_proxy_bypass_empty_entries(self):
1153
"""Ignore empty entries"""
1154
self.assertEvaluateProxyBypass(None, 'example.com', '')
1155
self.assertEvaluateProxyBypass(None, 'example.com', ',')
1156
self.assertEvaluateProxyBypass(None, 'example.com', 'foo,,bar')
1159
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
761
1160
"""Tests proxy server.
763
This MUST be used by daughter classes that also inherit from
764
TestCaseWithTwoWebservers.
766
We can't inherit directly from TestCaseWithTwoWebservers or
767
the test framework will try to create an instance which
768
cannot run, its implementation being incomplete.
770
1162
Be aware that we do not setup a real proxy here. Instead, we
771
1163
check that the *connection* goes through the proxy by serving
772
1164
different content (the faked proxy server append '-proxied'
773
1165
to the file names).
1168
scenarios = multiply_scenarios(
1169
vary_by_http_client_implementation(),
1170
vary_by_http_protocol_version(),
776
1173
# FIXME: We don't have an https server available, so we don't
777
# test https connections.
779
# FIXME: Once the test suite is better fitted to test
780
# authorization schemes, test proxy authorizations too (see
1174
# test https connections. --vila toolongago
783
1176
def setUp(self):
784
TestCaseWithTwoWebservers.setUp(self)
1177
super(TestProxyHttpServer, self).setUp()
1178
self.transport_secondary_server = http_utils.ProxyServer
785
1179
self.build_tree_contents([('foo', 'contents of foo\n'),
786
1180
('foo-proxied', 'proxied contents of foo\n')])
787
1181
# Let's setup some attributes for tests
788
self.server = self.get_readonly_server()
789
# FIXME: We should not rely on 'localhost' being the hostname
790
self.proxy_address = 'localhost:%d' % self.server.port
791
self.no_proxy_host = self.proxy_address
1182
server = self.get_readonly_server()
1183
self.server_host_port = '%s:%d' % (server.host, server.port)
1184
if self._testing_pycurl():
1185
# Oh my ! pycurl does not check for the port as part of
1186
# no_proxy :-( So we just test the host part
1187
self.no_proxy_host = server.host
1189
self.no_proxy_host = self.server_host_port
792
1190
# The secondary server is the proxy
793
self.proxy = self.get_secondary_server()
794
self.proxy_url = self.proxy.get_url()
797
def create_transport_secondary_server(self):
798
"""Creates an http server that will serve files with
799
'-proxied' appended to their names.
801
return HttpServer(FakeProxyRequestHandler)
803
def _set_and_capture_env_var(self, name, new_value):
804
"""Set an environment variable, and reset it when finished."""
805
self._old_env[name] = osutils.set_or_unset_env(name, new_value)
807
def _install_env(self, env):
808
for name, value in env.iteritems():
809
self._set_and_capture_env_var(name, value)
811
def _restore_env(self):
812
for name, value in self._old_env.iteritems():
813
osutils.set_or_unset_env(name, value)
815
def proxied_in_env(self, env):
816
self._install_env(env)
817
url = self.server.get_url()
818
t = self._transport(url)
820
self.assertEqual(t.get('foo').read(), 'proxied contents of foo\n')
824
def not_proxied_in_env(self, env):
825
self._install_env(env)
826
url = self.server.get_url()
827
t = self._transport(url)
829
self.assertEqual(t.get('foo').read(), 'contents of foo\n')
1191
self.proxy_url = self.get_secondary_url()
1193
def _testing_pycurl(self):
1194
# TODO: This is duplicated for lots of the classes in this file
1195
return (features.pycurl.available()
1196
and self._transport == PyCurlTransport)
1198
def assertProxied(self):
1199
t = self.get_readonly_transport()
1200
self.assertEqual('proxied contents of foo\n', t.get('foo').read())
1202
def assertNotProxied(self):
1203
t = self.get_readonly_transport()
1204
self.assertEqual('contents of foo\n', t.get('foo').read())
833
1206
def test_http_proxy(self):
834
self.proxied_in_env({'http_proxy': self.proxy_url})
1207
self.overrideEnv('http_proxy', self.proxy_url)
1208
self.assertProxied()
836
1210
def test_HTTP_PROXY(self):
837
self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
1211
if self._testing_pycurl():
1212
# pycurl does not check HTTP_PROXY for security reasons
1213
# (for use in a CGI context that we do not care
1214
# about. Should we ?)
1215
raise tests.TestNotApplicable(
1216
'pycurl does not check HTTP_PROXY for security reasons')
1217
self.overrideEnv('HTTP_PROXY', self.proxy_url)
1218
self.assertProxied()
839
1220
def test_all_proxy(self):
840
self.proxied_in_env({'all_proxy': self.proxy_url})
1221
self.overrideEnv('all_proxy', self.proxy_url)
1222
self.assertProxied()
842
1224
def test_ALL_PROXY(self):
843
self.proxied_in_env({'ALL_PROXY': self.proxy_url})
1225
self.overrideEnv('ALL_PROXY', self.proxy_url)
1226
self.assertProxied()
845
1228
def test_http_proxy_with_no_proxy(self):
846
self.not_proxied_in_env({'http_proxy': self.proxy_url,
847
'no_proxy': self.no_proxy_host})
1229
self.overrideEnv('no_proxy', self.no_proxy_host)
1230
self.overrideEnv('http_proxy', self.proxy_url)
1231
self.assertNotProxied()
849
1233
def test_HTTP_PROXY_with_NO_PROXY(self):
850
self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
851
'NO_PROXY': self.no_proxy_host})
1234
if self._testing_pycurl():
1235
raise tests.TestNotApplicable(
1236
'pycurl does not check HTTP_PROXY for security reasons')
1237
self.overrideEnv('NO_PROXY', self.no_proxy_host)
1238
self.overrideEnv('HTTP_PROXY', self.proxy_url)
1239
self.assertNotProxied()
853
1241
def test_all_proxy_with_no_proxy(self):
854
self.not_proxied_in_env({'all_proxy': self.proxy_url,
855
'no_proxy': self.no_proxy_host})
1242
self.overrideEnv('no_proxy', self.no_proxy_host)
1243
self.overrideEnv('all_proxy', self.proxy_url)
1244
self.assertNotProxied()
857
1246
def test_ALL_PROXY_with_NO_PROXY(self):
858
self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
859
'NO_PROXY': self.no_proxy_host})
861
def test_http_proxy_without_scheme(self):
862
self.assertRaises(errors.InvalidURL,
864
{'http_proxy': self.proxy_address})
867
class TestProxyHttpServer_urllib(TestProxyHttpServer,
868
TestCaseWithTwoWebservers):
869
"""Tests proxy server for urllib implementation"""
871
_transport = HttpTransport_urllib
874
class TestProxyHttpServer_pycurl(TestWithTransport_pycurl,
876
TestCaseWithTwoWebservers):
877
"""Tests proxy server for pycurl implementation"""
880
TestProxyHttpServer.setUp(self)
881
# Oh my ! pycurl does not check for the port as part of
882
# no_proxy :-( So we just test the host part
883
self.no_proxy_host = 'localhost'
885
def test_HTTP_PROXY(self):
886
# pycurl do not check HTTP_PROXY for security reasons
887
# (for use in a CGI context that we do not care
888
# about. Should we ?)
891
def test_HTTP_PROXY_with_NO_PROXY(self):
894
def test_http_proxy_without_scheme(self):
895
# pycurl *ignores* invalid proxy env variables. If that
896
# ever change in the future, this test will fail
897
# indicating that pycurl do not ignore anymore such
899
self.not_proxied_in_env({'http_proxy': self.proxy_address})
902
class TestRanges(object):
903
"""Test the Range header in GET methods..
905
This MUST be used by daughter classes that also inherit from
906
TestCaseWithWebserver.
908
We can't inherit directly from TestCaseWithWebserver or the
909
test framework will try to create an instance which cannot
910
run, its implementation being incomplete.
914
TestCaseWithWebserver.setUp(self)
1247
self.overrideEnv('NO_PROXY', self.no_proxy_host)
1248
self.overrideEnv('ALL_PROXY', self.proxy_url)
1249
self.assertNotProxied()
1251
def test_http_proxy_without_scheme(self):
1252
self.overrideEnv('http_proxy', self.server_host_port)
1253
if self._testing_pycurl():
1254
# pycurl *ignores* invalid proxy env variables. If that ever change
1255
# in the future, this test will fail indicating that pycurl do not
1256
# ignore anymore such variables.
1257
self.assertNotProxied()
1259
self.assertRaises(errors.InvalidURL, self.assertProxied)
1262
class TestRanges(http_utils.TestCaseWithWebserver):
1263
"""Test the Range header in GET methods."""
1265
scenarios = multiply_scenarios(
1266
vary_by_http_client_implementation(),
1267
vary_by_http_protocol_version(),
1271
http_utils.TestCaseWithWebserver.setUp(self)
915
1272
self.build_tree_contents([('a', '0123456789')],)
916
server = self.get_readonly_server()
917
self.transport = self._transport(server.get_url())
919
def _file_contents(self, relpath, ranges, tail_amount=0):
920
code, data = self.transport._get(relpath, ranges)
921
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
922
for start, end in ranges:
924
yield data.read(end - start + 1)
1274
def create_transport_readonly_server(self):
1275
return http_server.HttpServer(protocol_version=self._protocol_version)
1277
def _file_contents(self, relpath, ranges):
1278
t = self.get_readonly_transport()
1279
offsets = [ (start, end - start + 1) for start, end in ranges]
1280
coalesce = t._coalesce_offsets
1281
coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
1282
code, data = t._get(relpath, coalesced)
1283
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1284
for start, end in ranges:
1286
yield data.read(end - start + 1)
926
1288
def _file_tail(self, relpath, tail_amount):
927
code, data = self.transport._get(relpath, [], tail_amount)
928
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
929
data.seek(-tail_amount + 1, 2)
930
return data.read(tail_amount)
1289
t = self.get_readonly_transport()
1290
code, data = t._get(relpath, [], tail_amount)
1291
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1292
data.seek(-tail_amount, 2)
1293
return data.read(tail_amount)
932
1295
def test_range_header(self):
934
1297
map(self.assertEqual,['0', '234'],
935
1298
list(self._file_contents('a', [(0,0), (2,4)])),)
1300
def test_range_header_tail(self):
937
1301
self.assertEqual('789', self._file_tail('a', 3))
938
# Syntactically invalid range
939
self.assertRaises(errors.InvalidRange,
940
self.transport._get, 'a', [(4, 3)])
941
# Semantically invalid range
942
self.assertRaises(errors.InvalidRange,
943
self.transport._get, 'a', [(42, 128)])
946
class TestRanges_urllib(TestRanges, TestCaseWithWebserver):
947
"""Test the Range header in GET methods for urllib implementation"""
949
_transport = HttpTransport_urllib
952
class TestRanges_pycurl(TestWithTransport_pycurl,
954
TestCaseWithWebserver):
955
"""Test the Range header in GET methods for pycurl implementation"""
958
class TestHTTPRedirections(object):
959
"""Test redirection between http servers.
961
This MUST be used by daughter classes that also inherit from
962
TestCaseWithRedirectedWebserver.
964
We can't inherit directly from TestCaseWithTwoWebservers or the
965
test framework will try to create an instance which cannot
966
run, its implementation being incomplete.
969
def create_transport_secondary_server(self):
970
"""Create the secondary server redirecting to the primary server"""
971
new = self.get_readonly_server()
973
redirecting = HTTPServerRedirecting()
974
redirecting.redirect_to(new.host, new.port)
1303
def test_syntactically_invalid_range_header(self):
1304
self.assertListRaises(errors.InvalidHttpRange,
1305
self._file_contents, 'a', [(4, 3)])
1307
def test_semantically_invalid_range_header(self):
1308
self.assertListRaises(errors.InvalidHttpRange,
1309
self._file_contents, 'a', [(42, 128)])
1312
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1313
"""Test redirection between http servers."""
1315
scenarios = multiply_scenarios(
1316
vary_by_http_client_implementation(),
1317
vary_by_http_protocol_version(),
977
1320
def setUp(self):
978
1321
super(TestHTTPRedirections, self).setUp()
1144
1485
return self.old_transport.clone(exception.target)
1146
self.assertRaises(errors.TooManyRedirections, do_catching_redirections,
1487
self.assertRaises(errors.TooManyRedirections,
1488
transport.do_catching_redirections,
1147
1489
self.get_a, self.old_transport, redirected)
1492
class TestAuth(http_utils.TestCaseWithWebserver):
1493
"""Test authentication scheme"""
1495
scenarios = multiply_scenarios(
1496
vary_by_http_client_implementation(),
1497
vary_by_http_protocol_version(),
1498
vary_by_http_auth_scheme(),
1501
_auth_header = 'Authorization'
1502
_password_prompt_prefix = ''
1503
_username_prompt_prefix = ''
1508
super(TestAuth, self).setUp()
1509
self.server = self.get_readonly_server()
1510
self.build_tree_contents([('a', 'contents of a\n'),
1511
('b', 'contents of b\n'),])
1513
def create_transport_readonly_server(self):
1514
server = self._auth_server(protocol_version=self._protocol_version)
1515
server._url_protocol = self._url_protocol
1518
def _testing_pycurl(self):
1519
# TODO: This is duplicated for lots of the classes in this file
1520
return (features.pycurl.available()
1521
and self._transport == PyCurlTransport)
1523
def get_user_url(self, user, password):
1524
"""Build an url embedding user and password"""
1525
url = '%s://' % self.server._url_protocol
1526
if user is not None:
1528
if password is not None:
1529
url += ':' + password
1531
url += '%s:%s/' % (self.server.host, self.server.port)
1534
def get_user_transport(self, user, password):
1535
t = transport.get_transport(self.get_user_url(user, password))
1538
def test_no_user(self):
1539
self.server.add_user('joe', 'foo')
1540
t = self.get_user_transport(None, None)
1541
self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1542
# Only one 'Authentication Required' error should occur
1543
self.assertEqual(1, self.server.auth_required_errors)
1545
def test_empty_pass(self):
1546
self.server.add_user('joe', '')
1547
t = self.get_user_transport('joe', '')
1548
self.assertEqual('contents of a\n', t.get('a').read())
1549
# Only one 'Authentication Required' error should occur
1550
self.assertEqual(1, self.server.auth_required_errors)
1552
def test_user_pass(self):
1553
self.server.add_user('joe', 'foo')
1554
t = self.get_user_transport('joe', 'foo')
1555
self.assertEqual('contents of a\n', t.get('a').read())
1556
# Only one 'Authentication Required' error should occur
1557
self.assertEqual(1, self.server.auth_required_errors)
1559
def test_unknown_user(self):
1560
self.server.add_user('joe', 'foo')
1561
t = self.get_user_transport('bill', 'foo')
1562
self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1563
# Two 'Authentication Required' errors should occur (the
1564
# initial 'who are you' and 'I don't know you, who are
1566
self.assertEqual(2, self.server.auth_required_errors)
1568
def test_wrong_pass(self):
1569
self.server.add_user('joe', 'foo')
1570
t = self.get_user_transport('joe', 'bar')
1571
self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1572
# Two 'Authentication Required' errors should occur (the
1573
# initial 'who are you' and 'this is not you, who are you')
1574
self.assertEqual(2, self.server.auth_required_errors)
1576
def test_prompt_for_username(self):
1577
if self._testing_pycurl():
1578
raise tests.TestNotApplicable(
1579
'pycurl cannot prompt, it handles auth by embedding'
1580
' user:pass in urls only')
1582
self.server.add_user('joe', 'foo')
1583
t = self.get_user_transport(None, None)
1584
stdout = tests.StringIOWrapper()
1585
stderr = tests.StringIOWrapper()
1586
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
1587
stdout=stdout, stderr=stderr)
1588
self.assertEqual('contents of a\n',t.get('a').read())
1589
# stdin should be empty
1590
self.assertEqual('', ui.ui_factory.stdin.readline())
1592
expected_prompt = self._expected_username_prompt(t._unqualified_scheme)
1593
self.assertEqual(expected_prompt, stderr.read(len(expected_prompt)))
1594
self.assertEqual('', stdout.getvalue())
1595
self._check_password_prompt(t._unqualified_scheme, 'joe',
1598
def test_prompt_for_password(self):
1599
if self._testing_pycurl():
1600
raise tests.TestNotApplicable(
1601
'pycurl cannot prompt, it handles auth by embedding'
1602
' user:pass in urls only')
1604
self.server.add_user('joe', 'foo')
1605
t = self.get_user_transport('joe', None)
1606
stdout = tests.StringIOWrapper()
1607
stderr = tests.StringIOWrapper()
1608
ui.ui_factory = tests.TestUIFactory(stdin='foo\n',
1609
stdout=stdout, stderr=stderr)
1610
self.assertEqual('contents of a\n', t.get('a').read())
1611
# stdin should be empty
1612
self.assertEqual('', ui.ui_factory.stdin.readline())
1613
self._check_password_prompt(t._unqualified_scheme, 'joe',
1615
self.assertEqual('', stdout.getvalue())
1616
# And we shouldn't prompt again for a different request
1617
# against the same transport.
1618
self.assertEqual('contents of b\n',t.get('b').read())
1620
# And neither against a clone
1621
self.assertEqual('contents of b\n',t2.get('b').read())
1622
# Only one 'Authentication Required' error should occur
1623
self.assertEqual(1, self.server.auth_required_errors)
1625
def _check_password_prompt(self, scheme, user, actual_prompt):
1626
expected_prompt = (self._password_prompt_prefix
1627
+ ("%s %s@%s:%d, Realm: '%s' password: "
1629
user, self.server.host, self.server.port,
1630
self.server.auth_realm)))
1631
self.assertEqual(expected_prompt, actual_prompt)
1633
def _expected_username_prompt(self, scheme):
1634
return (self._username_prompt_prefix
1635
+ "%s %s:%d, Realm: '%s' username: " % (scheme.upper(),
1636
self.server.host, self.server.port,
1637
self.server.auth_realm))
1639
def test_no_prompt_for_password_when_using_auth_config(self):
1640
if self._testing_pycurl():
1641
raise tests.TestNotApplicable(
1642
'pycurl does not support authentication.conf'
1643
' since it cannot prompt')
1647
stdin_content = 'bar\n' # Not the right password
1648
self.server.add_user(user, password)
1649
t = self.get_user_transport(user, None)
1650
ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1651
stderr=tests.StringIOWrapper())
1652
# Create a minimal config file with the right password
1653
_setup_authentication_config(
1655
port=self.server.port,
1658
# Issue a request to the server to connect
1659
self.assertEqual('contents of a\n',t.get('a').read())
1660
# stdin should have been left untouched
1661
self.assertEqual(stdin_content, ui.ui_factory.stdin.readline())
1662
# Only one 'Authentication Required' error should occur
1663
self.assertEqual(1, self.server.auth_required_errors)
1665
def test_changing_nonce(self):
1666
if self._auth_server not in (http_utils.HTTPDigestAuthServer,
1667
http_utils.ProxyDigestAuthServer):
1668
raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
1669
if self._testing_pycurl():
1670
raise tests.KnownFailure(
1671
'pycurl does not handle a nonce change')
1672
self.server.add_user('joe', 'foo')
1673
t = self.get_user_transport('joe', 'foo')
1674
self.assertEqual('contents of a\n', t.get('a').read())
1675
self.assertEqual('contents of b\n', t.get('b').read())
1676
# Only one 'Authentication Required' error should have
1678
self.assertEqual(1, self.server.auth_required_errors)
1679
# The server invalidates the current nonce
1680
self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1681
self.assertEqual('contents of a\n', t.get('a').read())
1682
# Two 'Authentication Required' errors should occur (the
1683
# initial 'who are you' and a second 'who are you' with the new nonce)
1684
self.assertEqual(2, self.server.auth_required_errors)
1686
def test_user_from_auth_conf(self):
1687
if self._testing_pycurl():
1688
raise tests.TestNotApplicable(
1689
'pycurl does not support authentication.conf')
1692
self.server.add_user(user, password)
1693
_setup_authentication_config(
1695
port=self.server.port,
1698
t = self.get_user_transport(None, None)
1699
# Issue a request to the server to connect
1700
self.assertEqual('contents of a\n', t.get('a').read())
1701
# Only one 'Authentication Required' error should occur
1702
self.assertEqual(1, self.server.auth_required_errors)
1705
def _setup_authentication_config(**kwargs):
1706
conf = config.AuthenticationConfig()
1707
conf._get_config().update({'httptest': kwargs})
1712
class TestUrllib2AuthHandler(tests.TestCaseWithTransport):
1713
"""Unit tests for glue by which urllib2 asks us for authentication"""
1715
def test_get_user_password_without_port(self):
1716
"""We cope if urllib2 doesn't tell us the port.
1718
See https://bugs.launchpad.net/bzr/+bug/654684
1722
_setup_authentication_config(
1727
handler = _urllib2_wrappers.HTTPAuthHandler()
1728
got_pass = handler.get_user_password(dict(
1735
self.assertEquals((user, password), got_pass)
1738
class TestProxyAuth(TestAuth):
1739
"""Test proxy authentication schemes."""
1741
scenarios = multiply_scenarios(
1742
vary_by_http_client_implementation(),
1743
vary_by_http_protocol_version(),
1744
vary_by_http_proxy_auth_scheme(),
1747
_auth_header = 'Proxy-authorization'
1748
_password_prompt_prefix = 'Proxy '
1749
_username_prompt_prefix = 'Proxy '
1752
super(TestProxyAuth, self).setUp()
1753
# Override the contents to avoid false positives
1754
self.build_tree_contents([('a', 'not proxied contents of a\n'),
1755
('b', 'not proxied contents of b\n'),
1756
('a-proxied', 'contents of a\n'),
1757
('b-proxied', 'contents of b\n'),
1760
def get_user_transport(self, user, password):
1761
self.overrideEnv('all_proxy', self.get_user_url(user, password))
1762
return TestAuth.get_user_transport(self, user, password)
1764
def test_empty_pass(self):
1765
if self._testing_pycurl():
1767
if pycurl.version_info()[1] < '7.16.0':
1768
raise tests.KnownFailure(
1769
'pycurl < 7.16.0 does not handle empty proxy passwords')
1770
super(TestProxyAuth, self).test_empty_pass()
1773
class SampleSocket(object):
1774
"""A socket-like object for use in testing the HTTP request handler."""
1776
def __init__(self, socket_read_content):
1777
"""Constructs a sample socket.
1779
:param socket_read_content: a byte sequence
1781
# Use plain python StringIO so we can monkey-patch the close method to
1782
# not discard the contents.
1783
from StringIO import StringIO
1784
self.readfile = StringIO(socket_read_content)
1785
self.writefile = StringIO()
1786
self.writefile.close = lambda: None
1787
self.close = lambda: None
1789
def makefile(self, mode='r', bufsize=None):
1791
return self.readfile
1793
return self.writefile
1796
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1798
scenarios = multiply_scenarios(
1799
vary_by_http_client_implementation(),
1800
vary_by_http_protocol_version(),
1804
super(SmartHTTPTunnellingTest, self).setUp()
1805
# We use the VFS layer as part of HTTP tunnelling tests.
1806
self.overrideEnv('BZR_NO_SMART_VFS', None)
1807
self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1808
self.http_server = self.get_readonly_server()
1810
def create_transport_readonly_server(self):
1811
server = http_utils.HTTPServerWithSmarts(
1812
protocol_version=self._protocol_version)
1813
server._url_protocol = self._url_protocol
1816
def test_open_bzrdir(self):
1817
branch = self.make_branch('relpath')
1818
url = self.http_server.get_url() + 'relpath'
1819
bd = bzrdir.BzrDir.open(url)
1820
self.addCleanup(bd.transport.disconnect)
1821
self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1823
def test_bulk_data(self):
1824
# We should be able to send and receive bulk data in a single message.
1825
# The 'readv' command in the smart protocol both sends and receives
1826
# bulk data, so we use that.
1827
self.build_tree(['data-file'])
1828
http_transport = transport.get_transport(self.http_server.get_url())
1829
medium = http_transport.get_smart_medium()
1830
# Since we provide the medium, the url below will be mostly ignored
1831
# during the test, as long as the path is '/'.
1832
remote_transport = remote.RemoteTransport('bzr://fake_host/',
1835
[(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
1837
def test_http_send_smart_request(self):
1839
post_body = 'hello\n'
1840
expected_reply_body = 'ok\x012\n'
1842
http_transport = transport.get_transport(self.http_server.get_url())
1843
medium = http_transport.get_smart_medium()
1844
response = medium.send_http_smart_request(post_body)
1845
reply_body = response.read()
1846
self.assertEqual(expected_reply_body, reply_body)
1848
def test_smart_http_server_post_request_handler(self):
1849
httpd = self.http_server.server
1851
socket = SampleSocket(
1852
'POST /.bzr/smart %s \r\n' % self._protocol_version
1853
# HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
1855
+ 'Content-Length: 6\r\n'
1858
# Beware: the ('localhost', 80) below is the
1859
# client_address parameter, but we don't have one because
1860
# we have defined a socket which is not bound to an
1861
# address. The test framework never uses this client
1862
# address, so far...
1863
request_handler = http_utils.SmartRequestHandler(socket,
1866
response = socket.writefile.getvalue()
1867
self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
1868
# This includes the end of the HTTP headers, and all the body.
1869
expected_end_of_response = '\r\n\r\nok\x012\n'
1870
self.assertEndsWith(response, expected_end_of_response)
1873
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
1874
"""No smart server here request handler."""
1877
self.send_error(403, "Forbidden")
1880
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
1881
"""Test smart client behaviour against an http server without smarts."""
1883
_req_handler_class = ForbiddenRequestHandler
1885
def test_probe_smart_server(self):
1886
"""Test error handling against server refusing smart requests."""
1887
t = self.get_readonly_transport()
1888
# No need to build a valid smart request here, the server will not even
1889
# try to interpret it.
1890
self.assertRaises(errors.SmartProtocolError,
1891
t.get_smart_medium().send_http_smart_request,
1895
class Test_redirected_to(tests.TestCase):
1897
scenarios = vary_by_http_client_implementation()
1899
def test_redirected_to_subdir(self):
1900
t = self._transport('http://www.example.com/foo')
1901
r = t._redirected_to('http://www.example.com/foo',
1902
'http://www.example.com/foo/subdir')
1903
self.assertIsInstance(r, type(t))
1904
# Both transports share the some connection
1905
self.assertEqual(t._get_connection(), r._get_connection())
1907
def test_redirected_to_self_with_slash(self):
1908
t = self._transport('http://www.example.com/foo')
1909
r = t._redirected_to('http://www.example.com/foo',
1910
'http://www.example.com/foo/')
1911
self.assertIsInstance(r, type(t))
1912
# Both transports share the some connection (one can argue that we
1913
# should return the exact same transport here, but that seems
1915
self.assertEqual(t._get_connection(), r._get_connection())
1917
def test_redirected_to_host(self):
1918
t = self._transport('http://www.example.com/foo')
1919
r = t._redirected_to('http://www.example.com/foo',
1920
'http://foo.example.com/foo/subdir')
1921
self.assertIsInstance(r, type(t))
1923
def test_redirected_to_same_host_sibling_protocol(self):
1924
t = self._transport('http://www.example.com/foo')
1925
r = t._redirected_to('http://www.example.com/foo',
1926
'https://www.example.com/foo')
1927
self.assertIsInstance(r, type(t))
1929
def test_redirected_to_same_host_different_protocol(self):
1930
t = self._transport('http://www.example.com/foo')
1931
r = t._redirected_to('http://www.example.com/foo',
1932
'ftp://www.example.com/foo')
1933
self.assertNotEquals(type(r), type(t))
1935
def test_redirected_to_different_host_same_user(self):
1936
t = self._transport('http://joe@www.example.com/foo')
1937
r = t._redirected_to('http://www.example.com/foo',
1938
'https://foo.example.com/foo')
1939
self.assertIsInstance(r, type(t))
1940
self.assertEqual(t._user, r._user)
1943
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1944
"""Request handler for a unique and pre-defined request.
1946
The only thing we care about here is how many bytes travel on the wire. But
1947
since we want to measure it for a real http client, we have to send it
1950
We expect to receive a *single* request nothing more (and we won't even
1951
check what request it is, we just measure the bytes read until an empty
1955
def _handle_one_request(self):
1956
tcs = self.server.test_case_server
1957
requestline = self.rfile.readline()
1958
headers = self.MessageClass(self.rfile, 0)
1959
# We just read: the request, the headers, an empty line indicating the
1960
# end of the headers.
1961
bytes_read = len(requestline)
1962
for line in headers.headers:
1963
bytes_read += len(line)
1964
bytes_read += len('\r\n')
1965
if requestline.startswith('POST'):
1966
# The body should be a single line (or we don't know where it ends
1967
# and we don't want to issue a blocking read)
1968
body = self.rfile.readline()
1969
bytes_read += len(body)
1970
tcs.bytes_read = bytes_read
1972
# We set the bytes written *before* issuing the write, the client is
1973
# supposed to consume every produced byte *before* checking that value.
1975
# Doing the oppposite may lead to test failure: we may be interrupted
1976
# after the write but before updating the value. The client can then
1977
# continue and read the value *before* we can update it. And yes,
1978
# this has been observed -- vila 20090129
1979
tcs.bytes_written = len(tcs.canned_response)
1980
self.wfile.write(tcs.canned_response)
1983
class ActivityServerMixin(object):
1985
def __init__(self, protocol_version):
1986
super(ActivityServerMixin, self).__init__(
1987
request_handler=PredefinedRequestHandler,
1988
protocol_version=protocol_version)
1989
# Bytes read and written by the server
1991
self.bytes_written = 0
1992
self.canned_response = None
1995
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
1999
if tests.HTTPSServerFeature.available():
2000
from bzrlib.tests import https_server
2001
class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
2005
class TestActivityMixin(object):
2006
"""Test socket activity reporting.
2008
We use a special purpose server to control the bytes sent and received and
2009
be able to predict the activity on the client socket.
2013
tests.TestCase.setUp(self)
2014
self.server = self._activity_server(self._protocol_version)
2015
self.server.start_server()
2016
self.activities = {}
2017
def report_activity(t, bytes, direction):
2018
count = self.activities.get(direction, 0)
2020
self.activities[direction] = count
2022
# We override at class level because constructors may propagate the
2023
# bound method and render instance overriding ineffective (an
2024
# alternative would be to define a specific ui factory instead...)
2025
self.overrideAttr(self._transport, '_report_activity', report_activity)
2026
self.addCleanup(self.server.stop_server)
2028
def get_transport(self):
2029
t = self._transport(self.server.get_url())
2030
# FIXME: Needs cleanup -- vila 20100611
2033
def assertActivitiesMatch(self):
2034
self.assertEqual(self.server.bytes_read,
2035
self.activities.get('write', 0), 'written bytes')
2036
self.assertEqual(self.server.bytes_written,
2037
self.activities.get('read', 0), 'read bytes')
2040
self.server.canned_response = '''HTTP/1.1 200 OK\r
2041
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2042
Server: Apache/2.0.54 (Fedora)\r
2043
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2044
ETag: "56691-23-38e9ae00"\r
2045
Accept-Ranges: bytes\r
2046
Content-Length: 35\r
2048
Content-Type: text/plain; charset=UTF-8\r
2050
Bazaar-NG meta directory, format 1
2052
t = self.get_transport()
2053
self.assertEqual('Bazaar-NG meta directory, format 1\n',
2054
t.get('foo/bar').read())
2055
self.assertActivitiesMatch()
2058
self.server.canned_response = '''HTTP/1.1 200 OK\r
2059
Server: SimpleHTTP/0.6 Python/2.5.2\r
2060
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
2061
Content-type: application/octet-stream\r
2062
Content-Length: 20\r
2063
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
2066
t = self.get_transport()
2067
self.assertTrue(t.has('foo/bar'))
2068
self.assertActivitiesMatch()
2070
def test_readv(self):
2071
self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
2072
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
2073
Server: Apache/2.0.54 (Fedora)\r
2074
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
2075
ETag: "238a3c-16ec2-805c5540"\r
2076
Accept-Ranges: bytes\r
2077
Content-Length: 1534\r
2079
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
2082
--418470f848b63279b\r
2083
Content-type: text/plain; charset=UTF-8\r
2084
Content-range: bytes 0-254/93890\r
2086
mbp@sourcefrog.net-20050309040815-13242001617e4a06
2087
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
2088
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
2089
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
2090
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
2092
--418470f848b63279b\r
2093
Content-type: text/plain; charset=UTF-8\r
2094
Content-range: bytes 1000-2049/93890\r
2097
mbp@sourcefrog.net-20050311063625-07858525021f270b
2098
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
2099
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
2100
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
2101
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
2102
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
2103
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
2104
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
2105
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
2106
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
2107
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
2108
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
2109
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
2110
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
2111
mbp@sourcefrog.net-20050313120651-497bd231b19df600
2112
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
2113
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
2114
mbp@sourcefrog.net-20050314025539-637a636692c055cf
2115
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
2116
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
2118
--418470f848b63279b--\r
2120
t = self.get_transport()
2121
# Remember that the request is ignored and that the ranges below
2122
# doesn't have to match the canned response.
2123
l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
2124
self.assertEqual(2, len(l))
2125
self.assertActivitiesMatch()
2127
def test_post(self):
2128
self.server.canned_response = '''HTTP/1.1 200 OK\r
2129
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2130
Server: Apache/2.0.54 (Fedora)\r
2131
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2132
ETag: "56691-23-38e9ae00"\r
2133
Accept-Ranges: bytes\r
2134
Content-Length: 35\r
2136
Content-Type: text/plain; charset=UTF-8\r
2138
lalala whatever as long as itsssss
2140
t = self.get_transport()
2141
# We must send a single line of body bytes, see
2142
# PredefinedRequestHandler._handle_one_request
2143
code, f = t._post('abc def end-of-body\n')
2144
self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2145
self.assertActivitiesMatch()
2148
class TestActivity(tests.TestCase, TestActivityMixin):
2150
scenarios = multiply_scenarios(
2151
vary_by_http_activity(),
2152
vary_by_http_protocol_version(),
2156
TestActivityMixin.setUp(self)
2159
class TestNoReportActivity(tests.TestCase, TestActivityMixin):
2161
# Unlike TestActivity, we are really testing ReportingFileSocket and
2162
# ReportingSocket, so we don't need all the parametrization. Since
2163
# ReportingFileSocket and ReportingSocket are wrappers, it's easier to
2164
# test them through their use by the transport than directly (that's a
2165
# bit less clean but far more simpler and effective).
2166
_activity_server = ActivityHTTPServer
2167
_protocol_version = 'HTTP/1.1'
2170
self._transport =_urllib.HttpTransport_urllib
2171
TestActivityMixin.setUp(self)
2173
def assertActivitiesMatch(self):
2174
# Nothing to check here
2178
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
2179
"""Test authentication on the redirected http server."""
2181
scenarios = vary_by_http_protocol_version()
2183
_auth_header = 'Authorization'
2184
_password_prompt_prefix = ''
2185
_username_prompt_prefix = ''
2186
_auth_server = http_utils.HTTPBasicAuthServer
2187
_transport = _urllib.HttpTransport_urllib
2190
super(TestAuthOnRedirected, self).setUp()
2191
self.build_tree_contents([('a','a'),
2193
('1/a', 'redirected once'),
2195
new_prefix = 'http://%s:%s' % (self.new_server.host,
2196
self.new_server.port)
2197
self.old_server.redirections = [
2198
('(.*)', r'%s/1\1' % (new_prefix), 301),]
2199
self.old_transport = self.get_old_transport()
2200
self.new_server.add_user('joe', 'foo')
2201
cleanup_http_redirection_connections(self)
2203
def create_transport_readonly_server(self):
2204
server = self._auth_server(protocol_version=self._protocol_version)
2205
server._url_protocol = self._url_protocol
2211
def test_auth_on_redirected_via_do_catching_redirections(self):
2212
self.redirections = 0
2214
def redirected(t, exception, redirection_notice):
2215
self.redirections += 1
2216
redirected_t = t._redirected_to(exception.source, exception.target)
2217
self.addCleanup(redirected_t.disconnect)
2220
stdout = tests.StringIOWrapper()
2221
stderr = tests.StringIOWrapper()
2222
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
2223
stdout=stdout, stderr=stderr)
2224
self.assertEqual('redirected once',
2225
transport.do_catching_redirections(
2226
self.get_a, self.old_transport, redirected).read())
2227
self.assertEqual(1, self.redirections)
2228
# stdin should be empty
2229
self.assertEqual('', ui.ui_factory.stdin.readline())
2230
# stdout should be empty, stderr will contains the prompts
2231
self.assertEqual('', stdout.getvalue())
2233
def test_auth_on_redirected_via_following_redirections(self):
2234
self.new_server.add_user('joe', 'foo')
2235
stdout = tests.StringIOWrapper()
2236
stderr = tests.StringIOWrapper()
2237
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
2238
stdout=stdout, stderr=stderr)
2239
t = self.old_transport
2240
req = RedirectedRequest('GET', t.abspath('a'))
2241
new_prefix = 'http://%s:%s' % (self.new_server.host,
2242
self.new_server.port)
2243
self.old_server.redirections = [
2244
('(.*)', r'%s/1\1' % (new_prefix), 301),]
2245
self.assertEqual('redirected once', t._perform(req).read())
2246
# stdin should be empty
2247
self.assertEqual('', ui.ui_factory.stdin.readline())
2248
# stdout should be empty, stderr will contains the prompts
2249
self.assertEqual('', stdout.getvalue())