33
31
from bzrlib import (
39
remote as _mod_remote,
45
from bzrlib.tests import (
51
from bzrlib.tests.scenarios import (
52
load_tests_apply_scenarios,
39
from bzrlib.tests.HttpServer import (
44
from bzrlib.tests.HTTPTestUtil import (
45
BadProtocolRequestHandler,
46
BadStatusRequestHandler,
47
ForbiddenRequestHandler,
50
HTTPServerRedirecting,
51
InvalidStatusRequestHandler,
52
LimitedRangeHTTPServer,
53
NoRangeRequestHandler,
55
ProxyDigestAuthServer,
57
SingleRangeRequestHandler,
58
SingleOnlyRangeRequestHandler,
59
TestCaseWithRedirectedWebserver,
60
TestCaseWithTwoWebservers,
61
TestCaseWithWebserver,
55
64
from bzrlib.transport import (
66
do_catching_redirections,
59
70
from bzrlib.transport.http import (
65
if features.pycurl.available():
66
from bzrlib.transport.http._pycurl import PyCurlTransport
69
load_tests = load_tests_apply_scenarios
72
def vary_by_http_client_implementation():
73
"""Test the two libraries we can use, pycurl and urllib."""
74
transport_scenarios = [
75
('urllib', dict(_transport=_urllib.HttpTransport_urllib,
76
_server=http_server.HttpServer_urllib,
77
_url_protocol='http+urllib',)),
79
if features.pycurl.available():
80
transport_scenarios.append(
81
('pycurl', dict(_transport=PyCurlTransport,
82
_server=http_server.HttpServer_PyCurl,
83
_url_protocol='http+pycurl',)))
84
return transport_scenarios
87
def vary_by_http_protocol_version():
88
"""Test on http/1.0 and 1.1"""
90
('HTTP/1.0', dict(_protocol_version='HTTP/1.0')),
91
('HTTP/1.1', dict(_protocol_version='HTTP/1.1')),
95
def vary_by_http_auth_scheme():
97
('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
98
('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
100
dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
102
# Add some attributes common to all scenarios
103
for scenario_id, scenario_dict in scenarios:
104
scenario_dict.update(_auth_header='Authorization',
105
_username_prompt_prefix='',
106
_password_prompt_prefix='')
110
def vary_by_http_proxy_auth_scheme():
112
('proxy-basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
113
('proxy-digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
114
('proxy-basicdigest',
115
dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
117
# Add some attributes common to all scenarios
118
for scenario_id, scenario_dict in scenarios:
119
scenario_dict.update(_auth_header='Proxy-Authorization',
120
_username_prompt_prefix='Proxy ',
121
_password_prompt_prefix='Proxy ')
125
def vary_by_http_activity():
126
activity_scenarios = [
127
('urllib,http', dict(_activity_server=ActivityHTTPServer,
128
_transport=_urllib.HttpTransport_urllib,)),
130
if features.pycurl.available():
131
activity_scenarios.append(
132
('pycurl,http', dict(_activity_server=ActivityHTTPServer,
133
_transport=PyCurlTransport,)),)
134
if features.HTTPSServerFeature.available():
135
# FIXME: Until we have a better way to handle self-signed certificates
136
# (like allowing them in a test specific authentication.conf for
137
# example), we need some specialized pycurl/urllib transport for tests.
139
from bzrlib.tests import (
142
class HTTPS_urllib_transport(_urllib.HttpTransport_urllib):
144
def __init__(self, base, _from_transport=None):
145
super(HTTPS_urllib_transport, self).__init__(
146
base, _from_transport=_from_transport,
147
ca_certs=ssl_certs.build_path('ca.crt'))
149
activity_scenarios.append(
150
('urllib,https', dict(_activity_server=ActivityHTTPSServer,
151
_transport=HTTPS_urllib_transport,)),)
152
if features.pycurl.available():
153
class HTTPS_pycurl_transport(PyCurlTransport):
155
def __init__(self, base, _from_transport=None):
156
super(HTTPS_pycurl_transport, self).__init__(
157
base, _from_transport)
158
self.cabundle = str(ssl_certs.build_path('ca.crt'))
160
activity_scenarios.append(
161
('pycurl,https', dict(_activity_server=ActivityHTTPSServer,
162
_transport=HTTPS_pycurl_transport,)),)
163
return activity_scenarios
75
from bzrlib.transport.http._urllib import HttpTransport_urllib
76
from bzrlib.transport.http._urllib2_wrappers import (
166
82
class FakeManager(object):
191
107
self.received_bytes = ''
195
return '%s://%s:%s/' % (self.scheme, self.host, self.port)
197
def start_server(self):
198
110
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
199
111
self._sock.bind(('127.0.0.1', 0))
200
112
self.host, self.port = self._sock.getsockname()
201
113
self._ready = threading.Event()
202
self._thread = test_server.TestThread(
203
sync_event=self._ready, target=self._accept_read_and_reply)
114
self._thread = threading.Thread(target=self._accept_read_and_reply)
115
self._thread.setDaemon(True)
204
116
self._thread.start()
205
if 'threads' in tests.selftest_debug_flags:
206
sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
209
119
def _accept_read_and_reply(self):
210
120
self._sock.listen(1)
211
121
self._ready.set()
212
conn, address = self._sock.accept()
213
if self._expect_body_tail is not None:
122
self._sock.settimeout(5)
124
conn, address = self._sock.accept()
125
# On win32, the accepted connection will be non-blocking to start
126
# with because we're using settimeout.
127
conn.setblocking(True)
214
128
while not self.received_bytes.endswith(self._expect_body_tail):
215
129
self.received_bytes += conn.recv(4096)
216
130
conn.sendall('HTTP/1.1 200 OK\r\n')
131
except socket.timeout:
132
# Make sure the client isn't stuck waiting for us to e.g. accept.
218
133
self._sock.close()
219
134
except socket.error:
220
135
# The client may have already closed the socket.
223
def stop_server(self):
225
# Issue a fake connection to wake up the server and allow it to
227
fake_conn = osutils.connect_socket((self.host, self.port))
229
141
except socket.error:
230
142
# We might have already closed it. We don't care.
235
if 'threads' in tests.selftest_debug_flags:
236
sys.stderr.write('Thread joined: %s\n' % (self._thread.ident,))
239
class TestAuthHeader(tests.TestCase):
241
def parse_header(self, header, auth_handler_class=None):
242
if auth_handler_class is None:
243
auth_handler_class = _urllib2_wrappers.AbstractAuthHandler
244
self.auth_handler = auth_handler_class()
245
return self.auth_handler._parse_auth_header(header)
247
def test_empty_header(self):
248
scheme, remainder = self.parse_header('')
249
self.assertEqual('', scheme)
250
self.assertIs(None, remainder)
252
def test_negotiate_header(self):
253
scheme, remainder = self.parse_header('Negotiate')
254
self.assertEqual('negotiate', scheme)
255
self.assertIs(None, remainder)
257
def test_basic_header(self):
258
scheme, remainder = self.parse_header(
259
'Basic realm="Thou should not pass"')
260
self.assertEqual('basic', scheme)
261
self.assertEqual('realm="Thou should not pass"', remainder)
263
def test_basic_extract_realm(self):
264
scheme, remainder = self.parse_header(
265
'Basic realm="Thou should not pass"',
266
_urllib2_wrappers.BasicAuthHandler)
267
match, realm = self.auth_handler.extract_realm(remainder)
268
self.assertTrue(match is not None)
269
self.assertEqual('Thou should not pass', realm)
271
def test_digest_header(self):
272
scheme, remainder = self.parse_header(
273
'Digest realm="Thou should not pass"')
274
self.assertEqual('digest', scheme)
275
self.assertEqual('realm="Thou should not pass"', remainder)
278
class TestHTTPRangeParsing(tests.TestCase):
281
super(TestHTTPRangeParsing, self).setUp()
282
# We focus on range parsing here and ignore everything else
283
class RequestHandler(http_server.TestingHTTPRequestHandler):
284
def setup(self): pass
285
def handle(self): pass
286
def finish(self): pass
288
self.req_handler = RequestHandler(None, None, None)
290
def assertRanges(self, ranges, header, file_size):
291
self.assertEqual(ranges,
292
self.req_handler._parse_ranges(header, file_size))
294
def test_simple_range(self):
295
self.assertRanges([(0,2)], 'bytes=0-2', 12)
298
self.assertRanges([(8, 11)], 'bytes=-4', 12)
300
def test_tail_bigger_than_file(self):
301
self.assertRanges([(0, 11)], 'bytes=-99', 12)
303
def test_range_without_end(self):
304
self.assertRanges([(4, 11)], 'bytes=4-', 12)
306
def test_invalid_ranges(self):
307
self.assertRanges(None, 'bytes=12-22', 12)
308
self.assertRanges(None, 'bytes=1-3,12-22', 12)
309
self.assertRanges(None, 'bytes=-', 12)
312
class TestHTTPServer(tests.TestCase):
313
"""Test the HTTP servers implementations."""
315
def test_invalid_protocol(self):
316
class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
318
protocol_version = 'HTTP/0.1'
320
self.assertRaises(httplib.UnknownProtocol,
321
http_server.HttpServer, BogusRequestHandler)
323
def test_force_invalid_protocol(self):
324
self.assertRaises(httplib.UnknownProtocol,
325
http_server.HttpServer, protocol_version='HTTP/0.1')
327
def test_server_start_and_stop(self):
328
server = http_server.HttpServer()
329
self.addCleanup(server.stop_server)
330
server.start_server()
331
self.assertTrue(server.server is not None)
332
self.assertTrue(server.server.serving is not None)
333
self.assertTrue(server.server.serving)
335
def test_create_http_server_one_zero(self):
336
class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
338
protocol_version = 'HTTP/1.0'
340
server = http_server.HttpServer(RequestHandlerOneZero)
341
self.start_server(server)
342
self.assertIsInstance(server.server, http_server.TestingHTTPServer)
344
def test_create_http_server_one_one(self):
345
class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
347
protocol_version = 'HTTP/1.1'
349
server = http_server.HttpServer(RequestHandlerOneOne)
350
self.start_server(server)
351
self.assertIsInstance(server.server,
352
http_server.TestingThreadingHTTPServer)
354
def test_create_http_server_force_one_one(self):
355
class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
357
protocol_version = 'HTTP/1.0'
359
server = http_server.HttpServer(RequestHandlerOneZero,
360
protocol_version='HTTP/1.1')
361
self.start_server(server)
362
self.assertIsInstance(server.server,
363
http_server.TestingThreadingHTTPServer)
365
def test_create_http_server_force_one_zero(self):
366
class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
368
protocol_version = 'HTTP/1.1'
370
server = http_server.HttpServer(RequestHandlerOneOne,
371
protocol_version='HTTP/1.0')
372
self.start_server(server)
373
self.assertIsInstance(server.server,
374
http_server.TestingHTTPServer)
377
148
class TestWithTransport_pycurl(object):
378
149
"""Test case to inherit from if pycurl is present"""
380
151
def _get_pycurl_maybe(self):
381
self.requireFeature(features.pycurl)
382
return PyCurlTransport
153
from bzrlib.transport.http._pycurl import PyCurlTransport
154
return PyCurlTransport
155
except errors.DependencyNotPresent:
156
raise tests.TestSkipped('pycurl not present')
384
158
_transport = property(_get_pycurl_maybe)
387
class TestHttpTransportUrls(tests.TestCase):
388
"""Test the http urls."""
390
scenarios = vary_by_http_client_implementation()
161
class TestHttpUrls(tests.TestCase):
163
# TODO: This should be moved to authorization tests once they
166
def test_url_parsing(self):
168
url = extract_auth('http://example.com', f)
169
self.assertEquals('http://example.com', url)
170
self.assertEquals(0, len(f.credentials))
171
url = extract_auth('http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
172
self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
173
self.assertEquals(1, len(f.credentials))
174
self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
178
class TestHttpTransportUrls(object):
179
"""Test the http urls.
181
This MUST be used by daughter classes that also inherit from
184
We can't inherit directly from TestCase or the
185
test framework will try to create an instance which cannot
186
run, its implementation being incomplete.
392
189
def test_abs_url(self):
393
190
"""Construction of absolute http URLs"""
394
t = self._transport('http://example.com/bzr/bzr.dev/')
191
t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
395
192
eq = self.assertEqualDiff
396
eq(t.abspath('.'), 'http://example.com/bzr/bzr.dev')
397
eq(t.abspath('foo/bar'), 'http://example.com/bzr/bzr.dev/foo/bar')
398
eq(t.abspath('.bzr'), 'http://example.com/bzr/bzr.dev/.bzr')
193
eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
194
eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
195
eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
399
196
eq(t.abspath('.bzr/1//2/./3'),
400
'http://example.com/bzr/bzr.dev/.bzr/1/2/3')
197
'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
402
199
def test_invalid_http_urls(self):
403
200
"""Trap invalid construction of urls"""
404
self._transport('http://example.com/bzr/bzr.dev/')
201
t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
405
202
self.assertRaises(errors.InvalidURL,
407
'http://http://example.com/bzr/bzr.dev/')
204
'http://http://bazaar-vcs.org/bzr/bzr.dev/')
409
206
def test_http_root_urls(self):
410
207
"""Construction of URLs from server root"""
411
t = self._transport('http://example.com/')
208
t = self._transport('http://bzr.ozlabs.org/')
412
209
eq = self.assertEqualDiff
413
210
eq(t.abspath('.bzr/tree-version'),
414
'http://example.com/.bzr/tree-version')
211
'http://bzr.ozlabs.org/.bzr/tree-version')
416
213
def test_http_impl_urls(self):
417
214
"""There are servers which ask for particular clients to connect"""
418
215
server = self._server()
419
server.start_server()
421
218
url = server.get_url()
422
self.assertTrue(url.startswith('%s://' % self._url_protocol))
219
self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
427
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
224
class TestHttpUrls_urllib(TestHttpTransportUrls, tests.TestCase):
225
"""Test http urls with urllib"""
227
_transport = HttpTransport_urllib
228
_server = HttpServer_urllib
229
_qualified_prefix = 'http+urllib'
232
class TestHttpUrls_pycurl(TestWithTransport_pycurl, TestHttpTransportUrls,
234
"""Test http urls with pycurl"""
236
_server = HttpServer_PyCurl
237
_qualified_prefix = 'http+pycurl'
429
239
# TODO: This should really be moved into another pycurl
430
240
# specific test. When https tests will be implemented, take
585
class TestSpecificRequestHandler(http_utils.TestCaseWithWebserver):
586
"""Tests a specific request handler.
588
Daughter classes are expected to override _req_handler_class
591
scenarios = multiply_scenarios(
592
vary_by_http_client_implementation(),
593
vary_by_http_protocol_version(),
596
# Provide a useful default
597
_req_handler_class = http_server.TestingHTTPRequestHandler
599
def create_transport_readonly_server(self):
600
server = http_server.HttpServer(self._req_handler_class,
601
protocol_version=self._protocol_version)
602
server._url_protocol = self._url_protocol
605
def _testing_pycurl(self):
606
# TODO: This is duplicated for lots of the classes in this file
607
return (features.pycurl.available()
608
and self._transport == PyCurlTransport)
611
class WallRequestHandler(http_server.TestingHTTPRequestHandler):
612
"""Whatever request comes in, close the connection"""
614
def _handle_one_request(self):
615
"""Handle a single HTTP request, by abruptly closing the connection"""
616
self.close_connection = 1
619
class TestWallServer(TestSpecificRequestHandler):
426
class TestWallServer(object):
620
427
"""Tests exceptions during the connection phase"""
622
_req_handler_class = WallRequestHandler
429
def create_transport_readonly_server(self):
430
return HttpServer(WallRequestHandler)
624
432
def test_http_has(self):
625
t = self.get_readonly_transport()
433
server = self.get_readonly_server()
434
t = self._transport(server.get_url())
626
435
# Unfortunately httplib (see HTTPResponse._read_status
627
436
# for details) make no distinction between a closed
628
437
# socket and badly formatted status line, so we can't
629
438
# just test for ConnectionError, we have to test
630
# InvalidHttpResponse too. And pycurl may raise ConnectionReset
631
# instead of ConnectionError too.
632
self.assertRaises(( errors.ConnectionError, errors.ConnectionReset,
633
errors.InvalidHttpResponse),
439
# InvalidHttpResponse too.
440
self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
634
441
t.has, 'foo/bar')
636
443
def test_http_get(self):
637
t = self.get_readonly_transport()
638
self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
639
errors.InvalidHttpResponse),
444
server = self.get_readonly_server()
445
t = self._transport(server.get_url())
446
self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
640
447
t.get, 'foo/bar')
643
class BadStatusRequestHandler(http_server.TestingHTTPRequestHandler):
644
"""Whatever request comes in, returns a bad status"""
646
def parse_request(self):
647
"""Fakes handling a single HTTP request, returns a bad status"""
648
ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
649
self.send_response(0, "Bad status")
650
self.close_connection = 1
654
class TestBadStatusServer(TestSpecificRequestHandler):
450
class TestWallServer_urllib(TestWallServer, TestCaseWithWebserver):
451
"""Tests "wall" server for urllib implementation"""
453
_transport = HttpTransport_urllib
456
class TestWallServer_pycurl(TestWithTransport_pycurl,
458
TestCaseWithWebserver):
459
"""Tests "wall" server for pycurl implementation"""
462
class TestBadStatusServer(object):
655
463
"""Tests bad status from server."""
657
_req_handler_class = BadStatusRequestHandler
660
super(TestBadStatusServer, self).setUp()
661
# See https://bugs.launchpad.net/bzr/+bug/1451448 for details.
662
# TD;LR: Running both a TCP client and server in the same process and
663
# thread uncovers a race in python. The fix is to run the server in a
664
# different process. Trying to fix yet another race here is not worth
665
# the effort. -- vila 2015-09-06
666
if 'HTTP/1.0' in self.id():
667
raise tests.TestSkipped(
668
'Client/Server in the same process and thread can hang')
465
def create_transport_readonly_server(self):
466
return HttpServer(BadStatusRequestHandler)
670
468
def test_http_has(self):
671
t = self.get_readonly_transport()
672
self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
673
errors.InvalidHttpResponse),
469
server = self.get_readonly_server()
470
t = self._transport(server.get_url())
471
self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
676
473
def test_http_get(self):
677
t = self.get_readonly_transport()
678
self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
679
errors.InvalidHttpResponse),
683
class InvalidStatusRequestHandler(http_server.TestingHTTPRequestHandler):
684
"""Whatever request comes in, returns an invalid status"""
686
def parse_request(self):
687
"""Fakes handling a single HTTP request, returns a bad status"""
688
ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
689
self.wfile.write("Invalid status line\r\n")
690
# If we don't close the connection pycurl will hang. Since this is a
691
# stress test we don't *have* to respect the protocol, but we don't
692
# have to sabotage it too much either.
693
self.close_connection = True
474
server = self.get_readonly_server()
475
t = self._transport(server.get_url())
476
self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
479
class TestBadStatusServer_urllib(TestBadStatusServer, TestCaseWithWebserver):
480
"""Tests bad status server for urllib implementation"""
482
_transport = HttpTransport_urllib
485
class TestBadStatusServer_pycurl(TestWithTransport_pycurl,
487
TestCaseWithWebserver):
488
"""Tests bad status server for pycurl implementation"""
697
491
class TestInvalidStatusServer(TestBadStatusServer):
859
677
# The server should have issued 3 requests
860
678
self.assertEqual(3, server.GET_request_nb)
862
def test_complete_readv_leave_pipe_clean(self):
863
server = self.get_readonly_server()
864
t = self.get_readonly_transport()
865
# force transport to issue multiple requests
867
list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
868
# The server should have issued 3 requests
869
self.assertEqual(3, server.GET_request_nb)
870
self.assertEqual('0123456789', t.get_bytes('a'))
871
self.assertEqual(4, server.GET_request_nb)
873
def test_incomplete_readv_leave_pipe_clean(self):
874
server = self.get_readonly_server()
875
t = self.get_readonly_transport()
876
# force transport to issue multiple requests
878
# Don't collapse readv results into a list so that we leave unread
879
# bytes on the socket
880
ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
881
self.assertEqual((0, '0'), ireadv.next())
882
# The server should have issued one request so far
883
self.assertEqual(1, server.GET_request_nb)
884
self.assertEqual('0123456789', t.get_bytes('a'))
885
# get_bytes issued an additional request, the readv pending ones are
887
self.assertEqual(2, server.GET_request_nb)
890
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
891
"""Always reply to range request as if they were single.
893
Don't be explicit about it, just to annoy the clients.
896
def get_multiple_ranges(self, file, file_size, ranges):
897
"""Answer as if it was a single range request and ignores the rest"""
898
(start, end) = ranges[0]
899
return self.get_single_range(file, file_size, start, end)
902
681
class TestSingleRangeRequestServer(TestRangeRequestServer):
903
682
"""Test readv against a server which accept only single range requests"""
905
_req_handler_class = SingleRangeRequestHandler
908
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
909
"""Only reply to simple range requests, errors out on multiple"""
911
def get_multiple_ranges(self, file, file_size, ranges):
912
"""Refuses the multiple ranges request"""
915
self.send_error(416, "Requested range not satisfiable")
917
(start, end) = ranges[0]
918
return self.get_single_range(file, file_size, start, end)
684
def create_transport_readonly_server(self):
685
return HttpServer(SingleRangeRequestHandler)
688
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
689
TestCaseWithWebserver):
690
"""Tests single range requests accepting server for urllib implementation"""
692
_transport = HttpTransport_urllib
695
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
696
TestSingleRangeRequestServer,
697
TestCaseWithWebserver):
698
"""Tests single range requests accepting server for pycurl implementation"""
921
701
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
922
702
"""Test readv against a server which only accept single range requests"""
924
_req_handler_class = SingleOnlyRangeRequestHandler
927
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
928
"""Ignore range requests without notice"""
931
# Update the statistics
932
self.server.test_case_server.GET_request_nb += 1
933
# Just bypass the range handling done by TestingHTTPRequestHandler
934
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
704
def create_transport_readonly_server(self):
705
return HttpServer(SingleOnlyRangeRequestHandler)
708
class TestSingleOnlyRangeRequestServer_urllib(TestSingleOnlyRangeRequestServer,
709
TestCaseWithWebserver):
710
"""Tests single range requests accepting server for urllib implementation"""
712
_transport = HttpTransport_urllib
715
class TestSingleOnlyRangeRequestServer_pycurl(TestWithTransport_pycurl,
716
TestSingleOnlyRangeRequestServer,
717
TestCaseWithWebserver):
718
"""Tests single range requests accepting server for pycurl implementation"""
937
721
class TestNoRangeRequestServer(TestRangeRequestServer):
938
722
"""Test readv against a server which do not accept range requests"""
940
_req_handler_class = NoRangeRequestHandler
943
class MultipleRangeWithoutContentLengthRequestHandler(
944
http_server.TestingHTTPRequestHandler):
945
"""Reply to multiple range requests without content length header."""
947
def get_multiple_ranges(self, file, file_size, ranges):
948
self.send_response(206)
949
self.send_header('Accept-Ranges', 'bytes')
950
# XXX: this is strange; the 'random' name below seems undefined and
951
# yet the tests pass -- mbp 2010-10-11 bug 658773
952
boundary = "%d" % random.randint(0,0x7FFFFFFF)
953
self.send_header("Content-Type",
954
"multipart/byteranges; boundary=%s" % boundary)
956
for (start, end) in ranges:
957
self.wfile.write("--%s\r\n" % boundary)
958
self.send_header("Content-type", 'application/octet-stream')
959
self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
963
self.send_range_content(file, start, end - start + 1)
965
self.wfile.write("--%s\r\n" % boundary)
968
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
970
_req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
973
class TruncatedMultipleRangeRequestHandler(
974
http_server.TestingHTTPRequestHandler):
975
"""Reply to multiple range requests truncating the last ones.
977
This server generates responses whose Content-Length describes all the
978
ranges, but fail to include the last ones leading to client short reads.
979
This has been observed randomly with lighttpd (bug #179368).
724
def create_transport_readonly_server(self):
725
return HttpServer(NoRangeRequestHandler)
728
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
729
TestCaseWithWebserver):
730
"""Tests range requests refusing server for urllib implementation"""
732
_transport = HttpTransport_urllib
735
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
736
TestNoRangeRequestServer,
737
TestCaseWithWebserver):
738
"""Tests range requests refusing server for pycurl implementation"""
741
class TestLimitedRangeRequestServer(object):
742
"""Tests readv requests against server that errors out on too much ranges.
744
This MUST be used by daughter classes that also inherit from
745
TestCaseWithWebserver.
747
We can't inherit directly from TestCaseWithWebserver or the
748
test framework will try to create an instance which cannot
749
run, its implementation being incomplete.
982
_truncated_ranges = 2
984
def get_multiple_ranges(self, file, file_size, ranges):
985
self.send_response(206)
986
self.send_header('Accept-Ranges', 'bytes')
988
self.send_header('Content-Type',
989
'multipart/byteranges; boundary=%s' % boundary)
990
boundary_line = '--%s\r\n' % boundary
991
# Calculate the Content-Length
993
for (start, end) in ranges:
994
content_length += len(boundary_line)
995
content_length += self._header_line_length(
996
'Content-type', 'application/octet-stream')
997
content_length += self._header_line_length(
998
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
999
content_length += len('\r\n') # end headers
1000
content_length += end - start # + 1
1001
content_length += len(boundary_line)
1002
self.send_header('Content-length', content_length)
1005
# Send the multipart body
1007
for (start, end) in ranges:
1008
self.wfile.write(boundary_line)
1009
self.send_header('Content-type', 'application/octet-stream')
1010
self.send_header('Content-Range', 'bytes %d-%d/%d'
1011
% (start, end, file_size))
1013
if cur + self._truncated_ranges >= len(ranges):
1014
# Abruptly ends the response and close the connection
1015
self.close_connection = 1
1017
self.send_range_content(file, start, end - start + 1)
1020
self.wfile.write(boundary_line)
1023
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
1025
_req_handler_class = TruncatedMultipleRangeRequestHandler
1028
super(TestTruncatedMultipleRangeServer, self).setUp()
1029
self.build_tree_contents([('a', '0123456789')],)
1031
def test_readv_with_short_reads(self):
1032
server = self.get_readonly_server()
1033
t = self.get_readonly_transport()
1034
# Force separate ranges for each offset
1035
t._bytes_to_read_before_seek = 0
1036
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1037
self.assertEqual((0, '0'), ireadv.next())
1038
self.assertEqual((2, '2'), ireadv.next())
1039
if not self._testing_pycurl():
1040
# Only one request have been issued so far (except for pycurl that
1041
# try to read the whole response at once)
1042
self.assertEqual(1, server.GET_request_nb)
1043
self.assertEqual((4, '45'), ireadv.next())
1044
self.assertEqual((9, '9'), ireadv.next())
1045
# Both implementations issue 3 requests but:
1046
# - urllib does two multiple (4 ranges, then 2 ranges) then a single
1048
# - pycurl does two multiple (4 ranges, 4 ranges) then a single range
1049
self.assertEqual(3, server.GET_request_nb)
1050
# Finally the client have tried a single range request and stays in
1052
self.assertEqual('single', t._range_hint)
1055
class TruncatedBeforeBoundaryRequestHandler(
1056
http_server.TestingHTTPRequestHandler):
1057
"""Truncation before a boundary, like in bug 198646"""
1059
_truncated_ranges = 1
1061
def get_multiple_ranges(self, file, file_size, ranges):
1062
self.send_response(206)
1063
self.send_header('Accept-Ranges', 'bytes')
1065
self.send_header('Content-Type',
1066
'multipart/byteranges; boundary=%s' % boundary)
1067
boundary_line = '--%s\r\n' % boundary
1068
# Calculate the Content-Length
1070
for (start, end) in ranges:
1071
content_length += len(boundary_line)
1072
content_length += self._header_line_length(
1073
'Content-type', 'application/octet-stream')
1074
content_length += self._header_line_length(
1075
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
1076
content_length += len('\r\n') # end headers
1077
content_length += end - start # + 1
1078
content_length += len(boundary_line)
1079
self.send_header('Content-length', content_length)
1082
# Send the multipart body
1084
for (start, end) in ranges:
1085
if cur + self._truncated_ranges >= len(ranges):
1086
# Abruptly ends the response and close the connection
1087
self.close_connection = 1
1089
self.wfile.write(boundary_line)
1090
self.send_header('Content-type', 'application/octet-stream')
1091
self.send_header('Content-Range', 'bytes %d-%d/%d'
1092
% (start, end, file_size))
1094
self.send_range_content(file, start, end - start + 1)
1097
self.wfile.write(boundary_line)
1100
class TestTruncatedBeforeBoundary(TestSpecificRequestHandler):
1101
"""Tests the case of bug 198646, disconnecting before a boundary."""
1103
_req_handler_class = TruncatedBeforeBoundaryRequestHandler
1106
super(TestTruncatedBeforeBoundary, self).setUp()
1107
self.build_tree_contents([('a', '0123456789')],)
1109
def test_readv_with_short_reads(self):
1110
server = self.get_readonly_server()
1111
t = self.get_readonly_transport()
1112
# Force separate ranges for each offset
1113
t._bytes_to_read_before_seek = 0
1114
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1115
self.assertEqual((0, '0'), ireadv.next())
1116
self.assertEqual((2, '2'), ireadv.next())
1117
self.assertEqual((4, '45'), ireadv.next())
1118
self.assertEqual((9, '9'), ireadv.next())
1121
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1122
"""Errors out when range specifiers exceed the limit"""
1124
def get_multiple_ranges(self, file, file_size, ranges):
1125
"""Refuses the multiple ranges request"""
1126
tcs = self.server.test_case_server
1127
if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
1129
# Emulate apache behavior
1130
self.send_error(400, "Bad Request")
1132
return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
1133
self, file, file_size, ranges)
1136
class LimitedRangeHTTPServer(http_server.HttpServer):
1137
"""An HttpServer erroring out on requests with too much range specifiers"""
1139
def __init__(self, request_handler=LimitedRangeRequestHandler,
1140
protocol_version=None,
1142
http_server.HttpServer.__init__(self, request_handler,
1143
protocol_version=protocol_version)
1144
self.range_limit = range_limit
1147
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1148
"""Tests readv requests against a server erroring out on too much ranges."""
1150
scenarios = multiply_scenarios(
1151
vary_by_http_client_implementation(),
1152
vary_by_http_protocol_version(),
1155
# Requests with more range specifiers will error out
1158
754
def create_transport_readonly_server(self):
1159
return LimitedRangeHTTPServer(range_limit=self.range_limit,
1160
protocol_version=self._protocol_version)
755
# Requests with more range specifiers will error out
756
return LimitedRangeHTTPServer(range_limit=self.range_limit)
758
def get_transport(self):
759
return self._transport(self.get_readonly_server().get_url())
1162
761
def setUp(self):
1163
super(TestLimitedRangeRequestServer, self).setUp()
762
TestCaseWithWebserver.setUp(self)
1164
763
# We need to manipulate ranges that correspond to real chunks in the
1165
764
# response, so we build a content appropriately.
1166
765
filler = ''.join(['abcdefghij' for x in range(102)])
1168
767
self.build_tree_contents([('a', content)],)
1170
769
def test_few_ranges(self):
1171
t = self.get_readonly_transport()
770
t = self.get_transport()
1172
771
l = list(t.readv('a', ((0, 4), (1024, 4), )))
1173
772
self.assertEqual(l[0], (0, '0000'))
1174
773
self.assertEqual(l[1], (1024, '0001'))
1175
774
self.assertEqual(1, self.get_readonly_server().GET_request_nb)
1177
776
def test_more_ranges(self):
1178
t = self.get_readonly_transport()
777
t = self.get_transport()
1179
778
l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
1180
779
self.assertEqual(l[0], (0, '0000'))
1181
780
self.assertEqual(l[1], (1024, '0001'))
1182
781
self.assertEqual(l[2], (4096, '0004'))
1183
782
self.assertEqual(l[3], (8192, '0008'))
1184
783
# The server will refuse to serve the first request (too much ranges),
1185
# a second request will succeed.
784
# a second request will succeeds.
1186
785
self.assertEqual(2, self.get_readonly_server().GET_request_nb)
788
class TestLimitedRangeRequestServer_urllib(TestLimitedRangeRequestServer,
789
TestCaseWithWebserver):
790
"""Tests limited range requests server for urllib implementation"""
792
_transport = HttpTransport_urllib
795
class TestLimitedRangeRequestServer_pycurl(TestWithTransport_pycurl,
796
TestLimitedRangeRequestServer,
797
TestCaseWithWebserver):
798
"""Tests limited range requests server for pycurl implementation"""
1189
802
class TestHttpProxyWhiteBox(tests.TestCase):
1190
803
"""Whitebox test proxy http authorization.
1192
805
Only the urllib implementation is tested here.
809
tests.TestCase.setUp(self)
815
def _install_env(self, env):
816
for name, value in env.iteritems():
817
self._old_env[name] = osutils.set_or_unset_env(name, value)
819
def _restore_env(self):
820
for name, value in self._old_env.iteritems():
821
osutils.set_or_unset_env(name, value)
1195
823
def _proxied_request(self):
1196
handler = _urllib2_wrappers.ProxyHandler()
1197
request = _urllib2_wrappers.Request('GET', 'http://baz/buzzle')
824
handler = ProxyHandler()
825
request = Request('GET','http://baz/buzzle')
1198
826
handler.set_proxy(request, 'http')
1201
def assertEvaluateProxyBypass(self, expected, host, no_proxy):
1202
handler = _urllib2_wrappers.ProxyHandler()
1203
self.assertEqual(expected,
1204
handler.evaluate_proxy_bypass(host, no_proxy))
1206
829
def test_empty_user(self):
1207
self.overrideEnv('http_proxy', 'http://bar.com')
1208
request = self._proxied_request()
1209
self.assertFalse(request.headers.has_key('Proxy-authorization'))
1211
def test_user_with_at(self):
1212
self.overrideEnv('http_proxy',
1213
'http://username@domain:password@proxy_host:1234')
830
self._install_env({'http_proxy': 'http://bar.com'})
1214
831
request = self._proxied_request()
1215
832
self.assertFalse(request.headers.has_key('Proxy-authorization'))
1217
834
def test_invalid_proxy(self):
1218
835
"""A proxy env variable without scheme"""
1219
self.overrideEnv('http_proxy', 'host:1234')
836
self._install_env({'http_proxy': 'host:1234'})
1220
837
self.assertRaises(errors.InvalidURL, self._proxied_request)
1222
def test_evaluate_proxy_bypass_true(self):
1223
"""The host is not proxied"""
1224
self.assertEvaluateProxyBypass(True, 'example.com', 'example.com')
1225
self.assertEvaluateProxyBypass(True, 'bzr.example.com', '*example.com')
1227
def test_evaluate_proxy_bypass_false(self):
1228
"""The host is proxied"""
1229
self.assertEvaluateProxyBypass(False, 'bzr.example.com', None)
1231
def test_evaluate_proxy_bypass_unknown(self):
1232
"""The host is not explicitly proxied"""
1233
self.assertEvaluateProxyBypass(None, 'example.com', 'not.example.com')
1234
self.assertEvaluateProxyBypass(None, 'bzr.example.com', 'example.com')
1236
def test_evaluate_proxy_bypass_empty_entries(self):
1237
"""Ignore empty entries"""
1238
self.assertEvaluateProxyBypass(None, 'example.com', '')
1239
self.assertEvaluateProxyBypass(None, 'example.com', ',')
1240
self.assertEvaluateProxyBypass(None, 'example.com', 'foo,,bar')
1243
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
840
class TestProxyHttpServer(object):
1244
841
"""Tests proxy server.
843
This MUST be used by daughter classes that also inherit from
844
TestCaseWithTwoWebservers.
846
We can't inherit directly from TestCaseWithTwoWebservers or
847
the test framework will try to create an instance which
848
cannot run, its implementation being incomplete.
1246
850
Be aware that we do not setup a real proxy here. Instead, we
1247
851
check that the *connection* goes through the proxy by serving
1248
852
different content (the faked proxy server append '-proxied'
1249
853
to the file names).
1252
scenarios = multiply_scenarios(
1253
vary_by_http_client_implementation(),
1254
vary_by_http_protocol_version(),
1257
856
# FIXME: We don't have an https server available, so we don't
1258
# test https connections. --vila toolongago
857
# test https connections.
1260
859
def setUp(self):
1261
super(TestProxyHttpServer, self).setUp()
1262
self.transport_secondary_server = http_utils.ProxyServer
860
TestCaseWithTwoWebservers.setUp(self)
1263
861
self.build_tree_contents([('foo', 'contents of foo\n'),
1264
862
('foo-proxied', 'proxied contents of foo\n')])
1265
863
# Let's setup some attributes for tests
1266
server = self.get_readonly_server()
1267
self.server_host_port = '%s:%d' % (server.host, server.port)
1268
if self._testing_pycurl():
1269
# Oh my ! pycurl does not check for the port as part of
1270
# no_proxy :-( So we just test the host part
1271
self.no_proxy_host = server.host
1273
self.no_proxy_host = self.server_host_port
864
self.server = self.get_readonly_server()
865
self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
866
self.no_proxy_host = self.proxy_address
1274
867
# The secondary server is the proxy
1275
self.proxy_url = self.get_secondary_url()
1277
def _testing_pycurl(self):
1278
# TODO: This is duplicated for lots of the classes in this file
1279
return (features.pycurl.available()
1280
and self._transport == PyCurlTransport)
1282
def assertProxied(self):
1283
t = self.get_readonly_transport()
1284
self.assertEqual('proxied contents of foo\n', t.get('foo').read())
1286
def assertNotProxied(self):
1287
t = self.get_readonly_transport()
1288
self.assertEqual('contents of foo\n', t.get('foo').read())
868
self.proxy = self.get_secondary_server()
869
self.proxy_url = self.proxy.get_url()
872
def create_transport_secondary_server(self):
873
"""Creates an http server that will serve files with
874
'-proxied' appended to their names.
878
def _install_env(self, env):
879
for name, value in env.iteritems():
880
self._old_env[name] = osutils.set_or_unset_env(name, value)
882
def _restore_env(self):
883
for name, value in self._old_env.iteritems():
884
osutils.set_or_unset_env(name, value)
886
def proxied_in_env(self, env):
887
self._install_env(env)
888
url = self.server.get_url()
889
t = self._transport(url)
891
self.assertEqual(t.get('foo').read(), 'proxied contents of foo\n')
895
def not_proxied_in_env(self, env):
896
self._install_env(env)
897
url = self.server.get_url()
898
t = self._transport(url)
900
self.assertEqual(t.get('foo').read(), 'contents of foo\n')
1290
904
def test_http_proxy(self):
1291
self.overrideEnv('http_proxy', self.proxy_url)
1292
self.assertProxied()
905
self.proxied_in_env({'http_proxy': self.proxy_url})
1294
907
def test_HTTP_PROXY(self):
1295
if self._testing_pycurl():
1296
# pycurl does not check HTTP_PROXY for security reasons
1297
# (for use in a CGI context that we do not care
1298
# about. Should we ?)
1299
raise tests.TestNotApplicable(
1300
'pycurl does not check HTTP_PROXY for security reasons')
1301
self.overrideEnv('HTTP_PROXY', self.proxy_url)
1302
self.assertProxied()
908
self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
1304
910
def test_all_proxy(self):
1305
self.overrideEnv('all_proxy', self.proxy_url)
1306
self.assertProxied()
911
self.proxied_in_env({'all_proxy': self.proxy_url})
1308
913
def test_ALL_PROXY(self):
1309
self.overrideEnv('ALL_PROXY', self.proxy_url)
1310
self.assertProxied()
914
self.proxied_in_env({'ALL_PROXY': self.proxy_url})
1312
916
def test_http_proxy_with_no_proxy(self):
1313
self.overrideEnv('no_proxy', self.no_proxy_host)
1314
self.overrideEnv('http_proxy', self.proxy_url)
1315
self.assertNotProxied()
917
self.not_proxied_in_env({'http_proxy': self.proxy_url,
918
'no_proxy': self.no_proxy_host})
1317
920
def test_HTTP_PROXY_with_NO_PROXY(self):
1318
if self._testing_pycurl():
1319
raise tests.TestNotApplicable(
1320
'pycurl does not check HTTP_PROXY for security reasons')
1321
self.overrideEnv('NO_PROXY', self.no_proxy_host)
1322
self.overrideEnv('HTTP_PROXY', self.proxy_url)
1323
self.assertNotProxied()
921
self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
922
'NO_PROXY': self.no_proxy_host})
1325
924
def test_all_proxy_with_no_proxy(self):
1326
self.overrideEnv('no_proxy', self.no_proxy_host)
1327
self.overrideEnv('all_proxy', self.proxy_url)
1328
self.assertNotProxied()
925
self.not_proxied_in_env({'all_proxy': self.proxy_url,
926
'no_proxy': self.no_proxy_host})
1330
928
def test_ALL_PROXY_with_NO_PROXY(self):
1331
self.overrideEnv('NO_PROXY', self.no_proxy_host)
1332
self.overrideEnv('ALL_PROXY', self.proxy_url)
1333
self.assertNotProxied()
1335
def test_http_proxy_without_scheme(self):
1336
self.overrideEnv('http_proxy', self.server_host_port)
1337
if self._testing_pycurl():
1338
# pycurl *ignores* invalid proxy env variables. If that ever change
1339
# in the future, this test will fail indicating that pycurl do not
1340
# ignore anymore such variables.
1341
self.assertNotProxied()
1343
self.assertRaises(errors.InvalidURL, self.assertProxied)
1346
class TestRanges(http_utils.TestCaseWithWebserver):
1347
"""Test the Range header in GET methods."""
1349
scenarios = multiply_scenarios(
1350
vary_by_http_client_implementation(),
1351
vary_by_http_protocol_version(),
1355
super(TestRanges, self).setUp()
929
self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
930
'NO_PROXY': self.no_proxy_host})
932
def test_http_proxy_without_scheme(self):
933
self.assertRaises(errors.InvalidURL,
935
{'http_proxy': self.proxy_address})
938
class TestProxyHttpServer_urllib(TestProxyHttpServer,
939
TestCaseWithTwoWebservers):
940
"""Tests proxy server for urllib implementation"""
942
_transport = HttpTransport_urllib
945
class TestProxyHttpServer_pycurl(TestWithTransport_pycurl,
947
TestCaseWithTwoWebservers):
948
"""Tests proxy server for pycurl implementation"""
951
TestProxyHttpServer.setUp(self)
952
# Oh my ! pycurl does not check for the port as part of
953
# no_proxy :-( So we just test the host part
954
self.no_proxy_host = 'localhost'
956
def test_HTTP_PROXY(self):
957
# pycurl does not check HTTP_PROXY for security reasons
958
# (for use in a CGI context that we do not care
959
# about. Should we ?)
960
raise tests.TestNotApplicable(
961
'pycurl does not check HTTP_PROXY for security reasons')
963
def test_HTTP_PROXY_with_NO_PROXY(self):
964
raise tests.TestNotApplicable(
965
'pycurl does not check HTTP_PROXY for security reasons')
967
def test_http_proxy_without_scheme(self):
968
# pycurl *ignores* invalid proxy env variables. If that
969
# ever change in the future, this test will fail
970
# indicating that pycurl do not ignore anymore such
972
self.not_proxied_in_env({'http_proxy': self.proxy_address})
975
class TestRanges(object):
976
"""Test the Range header in GET methods..
978
This MUST be used by daughter classes that also inherit from
979
TestCaseWithWebserver.
981
We can't inherit directly from TestCaseWithWebserver or the
982
test framework will try to create an instance which cannot
983
run, its implementation being incomplete.
987
TestCaseWithWebserver.setUp(self)
1356
988
self.build_tree_contents([('a', '0123456789')],)
1358
def create_transport_readonly_server(self):
1359
return http_server.HttpServer(protocol_version=self._protocol_version)
989
server = self.get_readonly_server()
990
self.transport = self._transport(server.get_url())
1361
992
def _file_contents(self, relpath, ranges):
1362
t = self.get_readonly_transport()
1363
993
offsets = [ (start, end - start + 1) for start, end in ranges]
1364
coalesce = t._coalesce_offsets
994
coalesce = self.transport._coalesce_offsets
1365
995
coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
1366
code, data = t._get(relpath, coalesced)
996
code, data = self.transport._get(relpath, coalesced)
1367
997
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1368
998
for start, end in ranges:
1369
999
data.seek(start)
1370
1000
yield data.read(end - start + 1)
1372
1002
def _file_tail(self, relpath, tail_amount):
1373
t = self.get_readonly_transport()
1374
code, data = t._get(relpath, [], tail_amount)
1003
code, data = self.transport._get(relpath, [], tail_amount)
1375
1004
self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1376
1005
data.seek(-tail_amount, 2)
1377
1006
return data.read(tail_amount)
1495
1129
('5/a', 'redirected 5 times'),
1132
self.old_transport = self._transport(self.old_server.get_url())
1134
def setup_redirected_request(self):
1135
self.original_class = _urllib2_wrappers.Request
1136
_urllib2_wrappers.Request = RedirectedRequest
1138
def cleanup_redirected_request(self):
1139
_urllib2_wrappers.Request = self.original_class
1141
def create_transport_secondary_server(self):
1142
"""Create the secondary server, redirections are defined in the tests"""
1143
return HTTPServerRedirecting()
1498
1145
def test_one_redirection(self):
1499
t = self.get_old_transport()
1500
req = RedirectedRequest('GET', t._remote_path('a'))
1146
t = self.old_transport
1148
req = RedirectedRequest('GET', t.abspath('a'))
1149
req.follow_redirections = True
1501
1150
new_prefix = 'http://%s:%s' % (self.new_server.host,
1502
1151
self.new_server.port)
1503
1152
self.old_server.redirections = \
1504
1153
[('(.*)', r'%s/1\1' % (new_prefix), 301),]
1505
self.assertEqual('redirected once', t._perform(req).read())
1154
self.assertEquals('redirected once',t._perform(req).read())
1507
1156
def test_five_redirections(self):
1508
t = self.get_old_transport()
1509
req = RedirectedRequest('GET', t._remote_path('a'))
1157
t = self.old_transport
1159
req = RedirectedRequest('GET', t.abspath('a'))
1160
req.follow_redirections = True
1510
1161
old_prefix = 'http://%s:%s' % (self.old_server.host,
1511
1162
self.old_server.port)
1512
1163
new_prefix = 'http://%s:%s' % (self.new_server.host,
1513
1164
self.new_server.port)
1514
self.old_server.redirections = [
1515
('/1(.*)', r'%s/2\1' % (old_prefix), 302),
1516
('/2(.*)', r'%s/3\1' % (old_prefix), 303),
1517
('/3(.*)', r'%s/4\1' % (old_prefix), 307),
1518
('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1519
('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1521
self.assertEqual('redirected 5 times', t._perform(req).read())
1524
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
1525
"""Test transport.do_catching_redirections."""
1527
scenarios = multiply_scenarios(
1528
vary_by_http_client_implementation(),
1529
vary_by_http_protocol_version(),
1165
self.old_server.redirections = \
1166
[('/1(.*)', r'%s/2\1' % (old_prefix), 302),
1167
('/2(.*)', r'%s/3\1' % (old_prefix), 303),
1168
('/3(.*)', r'%s/4\1' % (old_prefix), 307),
1169
('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1170
('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1172
self.assertEquals('redirected 5 times',t._perform(req).read())
1175
class TestDoCatchRedirections(TestCaseWithRedirectedWebserver):
1176
"""Test transport.do_catching_redirections.
1178
We arbitrarily choose to use urllib transports
1181
_transport = HttpTransport_urllib
1532
1183
def setUp(self):
1533
1184
super(TestDoCatchRedirections, self).setUp()
1534
1185
self.build_tree_contents([('a', '0123456789'),],)
1535
cleanup_http_redirection_connections(self)
1537
self.old_transport = self.get_old_transport()
1187
self.old_transport = self._transport(self.old_server.get_url())
1189
def get_a(self, transport):
1190
return transport.get('a')
1542
1192
def test_no_redirection(self):
1543
t = self.get_new_transport()
1193
t = self._transport(self.new_server.get_url())
1545
1195
# We use None for redirected so that we fail if redirected
1546
self.assertEqual('0123456789',
1547
transport.do_catching_redirections(
1548
self.get_a, t, None).read())
1196
self.assertEquals('0123456789',
1197
do_catching_redirections(self.get_a, t, None).read())
1550
1199
def test_one_redirection(self):
1551
1200
self.redirections = 0
1553
def redirected(t, exception, redirection_notice):
1202
def redirected(transport, exception, redirection_notice):
1554
1203
self.redirections += 1
1555
redirected_t = t._redirected_to(exception.source, exception.target)
1204
dir, file = urlutils.split(exception.target)
1205
return self._transport(dir)
1558
self.assertEqual('0123456789',
1559
transport.do_catching_redirections(
1560
self.get_a, self.old_transport, redirected).read())
1561
self.assertEqual(1, self.redirections)
1207
self.assertEquals('0123456789',
1208
do_catching_redirections(self.get_a,
1212
self.assertEquals(1, self.redirections)
1563
1214
def test_redirection_loop(self):
1767
1341
# Only one 'Authentication Required' error should occur
1768
1342
self.assertEqual(1, self.server.auth_required_errors)
1770
def test_changing_nonce(self):
1771
if self._auth_server not in (http_utils.HTTPDigestAuthServer,
1772
http_utils.ProxyDigestAuthServer):
1773
raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
1774
if self._testing_pycurl():
1776
'pycurl does not handle a nonce change')
1777
self.server.add_user('joe', 'foo')
1778
t = self.get_user_transport('joe', 'foo')
1779
self.assertEqual('contents of a\n', t.get('a').read())
1780
self.assertEqual('contents of b\n', t.get('b').read())
1781
# Only one 'Authentication Required' error should have
1783
self.assertEqual(1, self.server.auth_required_errors)
1784
# The server invalidates the current nonce
1785
self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1786
self.assertEqual('contents of a\n', t.get('a').read())
1787
# Two 'Authentication Required' errors should occur (the
1788
# initial 'who are you' and a second 'who are you' with the new nonce)
1789
self.assertEqual(2, self.server.auth_required_errors)
1791
def test_user_from_auth_conf(self):
1792
if self._testing_pycurl():
1793
raise tests.TestNotApplicable(
1794
'pycurl does not support authentication.conf')
1797
self.server.add_user(user, password)
1798
_setup_authentication_config(scheme='http', port=self.server.port,
1799
user=user, password=password)
1800
t = self.get_user_transport(None, None)
1801
# Issue a request to the server to connect
1802
self.assertEqual('contents of a\n', t.get('a').read())
1803
# Only one 'Authentication Required' error should occur
1804
self.assertEqual(1, self.server.auth_required_errors)
1806
def test_no_credential_leaks_in_log(self):
1807
self.overrideAttr(debug, 'debug_flags', set(['http']))
1809
password = 'very-sensitive-password'
1810
self.server.add_user(user, password)
1811
t = self.get_user_transport(user, password)
1812
# Capture the debug calls to mutter
1815
lines = args[0] % args[1:]
1816
# Some calls output multiple lines, just split them now since we
1817
# care about a single one later.
1818
self.mutters.extend(lines.splitlines())
1819
self.overrideAttr(trace, 'mutter', mutter)
1820
# Issue a request to the server to connect
1821
self.assertEqual(True, t.has('a'))
1822
# Only one 'Authentication Required' error should occur
1823
self.assertEqual(1, self.server.auth_required_errors)
1824
# Since the authentification succeeded, there should be a corresponding
1826
sent_auth_headers = [line for line in self.mutters
1827
if line.startswith('> %s' % (self._auth_header,))]
1828
self.assertLength(1, sent_auth_headers)
1829
self.assertStartsWith(sent_auth_headers[0],
1830
'> %s: <masked>' % (self._auth_header,))
1346
class TestHTTPAuth(TestAuth):
1347
"""Test HTTP authentication schemes.
1349
Daughter classes MUST inherit from TestCaseWithWebserver too.
1352
_auth_header = 'Authorization'
1355
TestCaseWithWebserver.setUp(self)
1356
self.server = self.get_readonly_server()
1357
TestAuth.setUp(self)
1359
def get_user_transport(self, user=None, password=None):
1360
return self._transport(self.get_user_url(user, password))
1833
1363
class TestProxyAuth(TestAuth):
1834
1364
"""Test proxy authentication schemes.
1836
This inherits from TestAuth to tweak the setUp and filter some failing
1366
Daughter classes MUST also inherit from TestCaseWithWebserver.
1368
_auth_header = 'Proxy-authorization'
1369
_password_prompt_prefix = 'Proxy '
1840
scenarios = multiply_scenarios(
1841
vary_by_http_client_implementation(),
1842
vary_by_http_protocol_version(),
1843
vary_by_http_proxy_auth_scheme(),
1846
1372
def setUp(self):
1847
super(TestProxyAuth, self).setUp()
1373
TestCaseWithWebserver.setUp(self)
1374
self.server = self.get_readonly_server()
1376
self.addCleanup(self._restore_env)
1377
TestAuth.setUp(self)
1848
1378
# Override the contents to avoid false positives
1849
1379
self.build_tree_contents([('a', 'not proxied contents of a\n'),
1850
1380
('b', 'not proxied contents of b\n'),
1852
1382
('b-proxied', 'contents of b\n'),
1855
def get_user_transport(self, user, password):
1856
self.overrideEnv('all_proxy', self.get_user_url(user, password))
1857
return TestAuth.get_user_transport(self, user, password)
1859
def test_empty_pass(self):
1860
if self._testing_pycurl():
1862
if pycurl.version_info()[1] < '7.16.0':
1864
'pycurl < 7.16.0 does not handle empty proxy passwords')
1865
super(TestProxyAuth, self).test_empty_pass()
1868
class SampleSocket(object):
1869
"""A socket-like object for use in testing the HTTP request handler."""
1871
def __init__(self, socket_read_content):
1872
"""Constructs a sample socket.
1874
:param socket_read_content: a byte sequence
1876
# Use plain python StringIO so we can monkey-patch the close method to
1877
# not discard the contents.
1878
from StringIO import StringIO
1879
self.readfile = StringIO(socket_read_content)
1880
self.writefile = StringIO()
1881
self.writefile.close = lambda: None
1882
self.close = lambda: None
1884
def makefile(self, mode='r', bufsize=None):
1886
return self.readfile
1888
return self.writefile
1891
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1893
scenarios = multiply_scenarios(
1894
vary_by_http_client_implementation(),
1895
vary_by_http_protocol_version(),
1899
super(SmartHTTPTunnellingTest, self).setUp()
1900
# We use the VFS layer as part of HTTP tunnelling tests.
1901
self.overrideEnv('BZR_NO_SMART_VFS', None)
1902
self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1903
self.http_server = self.get_readonly_server()
1905
def create_transport_readonly_server(self):
1906
server = http_utils.HTTPServerWithSmarts(
1907
protocol_version=self._protocol_version)
1908
server._url_protocol = self._url_protocol
1911
def test_open_controldir(self):
1912
branch = self.make_branch('relpath')
1913
url = self.http_server.get_url() + 'relpath'
1914
bd = controldir.ControlDir.open(url)
1915
self.addCleanup(bd.transport.disconnect)
1916
self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1918
def test_bulk_data(self):
1919
# We should be able to send and receive bulk data in a single message.
1920
# The 'readv' command in the smart protocol both sends and receives
1921
# bulk data, so we use that.
1922
self.build_tree(['data-file'])
1923
http_transport = transport.get_transport_from_url(
1924
self.http_server.get_url())
1925
medium = http_transport.get_smart_medium()
1926
# Since we provide the medium, the url below will be mostly ignored
1927
# during the test, as long as the path is '/'.
1928
remote_transport = remote.RemoteTransport('bzr://fake_host/',
1931
[(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
1933
def test_http_send_smart_request(self):
1935
post_body = 'hello\n'
1936
expected_reply_body = 'ok\x012\n'
1938
http_transport = transport.get_transport_from_url(
1939
self.http_server.get_url())
1940
medium = http_transport.get_smart_medium()
1941
response = medium.send_http_smart_request(post_body)
1942
reply_body = response.read()
1943
self.assertEqual(expected_reply_body, reply_body)
1945
def test_smart_http_server_post_request_handler(self):
1946
httpd = self.http_server.server
1948
socket = SampleSocket(
1949
'POST /.bzr/smart %s \r\n' % self._protocol_version
1950
# HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
1952
+ 'Content-Length: 6\r\n'
1955
# Beware: the ('localhost', 80) below is the
1956
# client_address parameter, but we don't have one because
1957
# we have defined a socket which is not bound to an
1958
# address. The test framework never uses this client
1959
# address, so far...
1960
request_handler = http_utils.SmartRequestHandler(socket,
1963
response = socket.writefile.getvalue()
1964
self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
1965
# This includes the end of the HTTP headers, and all the body.
1966
expected_end_of_response = '\r\n\r\nok\x012\n'
1967
self.assertEndsWith(response, expected_end_of_response)
1970
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
1971
"""No smart server here request handler."""
1974
self.send_error(403, "Forbidden")
1977
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
1978
"""Test smart client behaviour against an http server without smarts."""
1980
_req_handler_class = ForbiddenRequestHandler
1982
def test_probe_smart_server(self):
1983
"""Test error handling against server refusing smart requests."""
1984
t = self.get_readonly_transport()
1985
# No need to build a valid smart request here, the server will not even
1986
# try to interpret it.
1987
self.assertRaises(errors.SmartProtocolError,
1988
t.get_smart_medium().send_http_smart_request,
1992
class Test_redirected_to(tests.TestCase):
1994
scenarios = vary_by_http_client_implementation()
1996
def test_redirected_to_subdir(self):
1997
t = self._transport('http://www.example.com/foo')
1998
r = t._redirected_to('http://www.example.com/foo',
1999
'http://www.example.com/foo/subdir')
2000
self.assertIsInstance(r, type(t))
2001
# Both transports share the some connection
2002
self.assertEqual(t._get_connection(), r._get_connection())
2003
self.assertEqual('http://www.example.com/foo/subdir/', r.base)
2005
def test_redirected_to_self_with_slash(self):
2006
t = self._transport('http://www.example.com/foo')
2007
r = t._redirected_to('http://www.example.com/foo',
2008
'http://www.example.com/foo/')
2009
self.assertIsInstance(r, type(t))
2010
# Both transports share the some connection (one can argue that we
2011
# should return the exact same transport here, but that seems
2013
self.assertEqual(t._get_connection(), r._get_connection())
2015
def test_redirected_to_host(self):
2016
t = self._transport('http://www.example.com/foo')
2017
r = t._redirected_to('http://www.example.com/foo',
2018
'http://foo.example.com/foo/subdir')
2019
self.assertIsInstance(r, type(t))
2020
self.assertEqual('http://foo.example.com/foo/subdir/',
2023
def test_redirected_to_same_host_sibling_protocol(self):
2024
t = self._transport('http://www.example.com/foo')
2025
r = t._redirected_to('http://www.example.com/foo',
2026
'https://www.example.com/foo')
2027
self.assertIsInstance(r, type(t))
2028
self.assertEqual('https://www.example.com/foo/',
2031
def test_redirected_to_same_host_different_protocol(self):
2032
t = self._transport('http://www.example.com/foo')
2033
r = t._redirected_to('http://www.example.com/foo',
2034
'ftp://www.example.com/foo')
2035
self.assertNotEqual(type(r), type(t))
2036
self.assertEqual('ftp://www.example.com/foo/', r.external_url())
2038
def test_redirected_to_same_host_specific_implementation(self):
2039
t = self._transport('http://www.example.com/foo')
2040
r = t._redirected_to('http://www.example.com/foo',
2041
'https+urllib://www.example.com/foo')
2042
self.assertEqual('https://www.example.com/foo/', r.external_url())
2044
def test_redirected_to_different_host_same_user(self):
2045
t = self._transport('http://joe@www.example.com/foo')
2046
r = t._redirected_to('http://www.example.com/foo',
2047
'https://foo.example.com/foo')
2048
self.assertIsInstance(r, type(t))
2049
self.assertEqual(t._parsed_url.user, r._parsed_url.user)
2050
self.assertEqual('https://joe@foo.example.com/foo/', r.external_url())
2053
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
2054
"""Request handler for a unique and pre-defined request.
2056
The only thing we care about here is how many bytes travel on the wire. But
2057
since we want to measure it for a real http client, we have to send it
2060
We expect to receive a *single* request nothing more (and we won't even
2061
check what request it is, we just measure the bytes read until an empty
2065
def _handle_one_request(self):
2066
tcs = self.server.test_case_server
2067
requestline = self.rfile.readline()
2068
headers = self.MessageClass(self.rfile, 0)
2069
# We just read: the request, the headers, an empty line indicating the
2070
# end of the headers.
2071
bytes_read = len(requestline)
2072
for line in headers.headers:
2073
bytes_read += len(line)
2074
bytes_read += len('\r\n')
2075
if requestline.startswith('POST'):
2076
# The body should be a single line (or we don't know where it ends
2077
# and we don't want to issue a blocking read)
2078
body = self.rfile.readline()
2079
bytes_read += len(body)
2080
tcs.bytes_read = bytes_read
2082
# We set the bytes written *before* issuing the write, the client is
2083
# supposed to consume every produced byte *before* checking that value.
2085
# Doing the oppposite may lead to test failure: we may be interrupted
2086
# after the write but before updating the value. The client can then
2087
# continue and read the value *before* we can update it. And yes,
2088
# this has been observed -- vila 20090129
2089
tcs.bytes_written = len(tcs.canned_response)
2090
self.wfile.write(tcs.canned_response)
2093
class ActivityServerMixin(object):
2095
def __init__(self, protocol_version):
2096
super(ActivityServerMixin, self).__init__(
2097
request_handler=PredefinedRequestHandler,
2098
protocol_version=protocol_version)
2099
# Bytes read and written by the server
2101
self.bytes_written = 0
2102
self.canned_response = None
2105
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
2109
if features.HTTPSServerFeature.available():
2110
from bzrlib.tests import https_server
2111
class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
2115
class TestActivityMixin(object):
2116
"""Test socket activity reporting.
2118
We use a special purpose server to control the bytes sent and received and
2119
be able to predict the activity on the client socket.
2123
self.server = self._activity_server(self._protocol_version)
2124
self.server.start_server()
2125
self.addCleanup(self.server.stop_server)
2126
_activities = {} # Don't close over self and create a cycle
2127
def report_activity(t, bytes, direction):
2128
count = _activities.get(direction, 0)
2130
_activities[direction] = count
2131
self.activities = _activities
2132
# We override at class level because constructors may propagate the
2133
# bound method and render instance overriding ineffective (an
2134
# alternative would be to define a specific ui factory instead...)
2135
self.overrideAttr(self._transport, '_report_activity', report_activity)
2137
def get_transport(self):
2138
t = self._transport(self.server.get_url())
2139
# FIXME: Needs cleanup -- vila 20100611
2142
def assertActivitiesMatch(self):
2143
self.assertEqual(self.server.bytes_read,
2144
self.activities.get('write', 0), 'written bytes')
2145
self.assertEqual(self.server.bytes_written,
2146
self.activities.get('read', 0), 'read bytes')
2149
self.server.canned_response = '''HTTP/1.1 200 OK\r
2150
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2151
Server: Apache/2.0.54 (Fedora)\r
2152
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2153
ETag: "56691-23-38e9ae00"\r
2154
Accept-Ranges: bytes\r
2155
Content-Length: 35\r
2157
Content-Type: text/plain; charset=UTF-8\r
2159
Bazaar-NG meta directory, format 1
2161
t = self.get_transport()
2162
self.assertEqual('Bazaar-NG meta directory, format 1\n',
2163
t.get('foo/bar').read())
2164
self.assertActivitiesMatch()
2167
self.server.canned_response = '''HTTP/1.1 200 OK\r
2168
Server: SimpleHTTP/0.6 Python/2.5.2\r
2169
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
2170
Content-type: application/octet-stream\r
2171
Content-Length: 20\r
2172
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
2175
t = self.get_transport()
2176
self.assertTrue(t.has('foo/bar'))
2177
self.assertActivitiesMatch()
2179
def test_readv(self):
2180
self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
2181
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
2182
Server: Apache/2.0.54 (Fedora)\r
2183
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
2184
ETag: "238a3c-16ec2-805c5540"\r
2185
Accept-Ranges: bytes\r
2186
Content-Length: 1534\r
2188
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
2191
--418470f848b63279b\r
2192
Content-type: text/plain; charset=UTF-8\r
2193
Content-range: bytes 0-254/93890\r
2195
mbp@sourcefrog.net-20050309040815-13242001617e4a06
2196
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
2197
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
2198
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
2199
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
2201
--418470f848b63279b\r
2202
Content-type: text/plain; charset=UTF-8\r
2203
Content-range: bytes 1000-2049/93890\r
2206
mbp@sourcefrog.net-20050311063625-07858525021f270b
2207
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
2208
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
2209
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
2210
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
2211
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
2212
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
2213
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
2214
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
2215
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
2216
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
2217
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
2218
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
2219
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
2220
mbp@sourcefrog.net-20050313120651-497bd231b19df600
2221
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
2222
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
2223
mbp@sourcefrog.net-20050314025539-637a636692c055cf
2224
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
2225
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
2227
--418470f848b63279b--\r
2229
t = self.get_transport()
2230
# Remember that the request is ignored and that the ranges below
2231
# doesn't have to match the canned response.
2232
l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
2233
self.assertEqual(2, len(l))
2234
self.assertActivitiesMatch()
2236
def test_post(self):
2237
self.server.canned_response = '''HTTP/1.1 200 OK\r
2238
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2239
Server: Apache/2.0.54 (Fedora)\r
2240
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2241
ETag: "56691-23-38e9ae00"\r
2242
Accept-Ranges: bytes\r
2243
Content-Length: 35\r
2245
Content-Type: text/plain; charset=UTF-8\r
2247
lalala whatever as long as itsssss
2249
t = self.get_transport()
2250
# We must send a single line of body bytes, see
2251
# PredefinedRequestHandler._handle_one_request
2252
code, f = t._post('abc def end-of-body\n')
2253
self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2254
self.assertActivitiesMatch()
2257
class TestActivity(tests.TestCase, TestActivityMixin):
2259
scenarios = multiply_scenarios(
2260
vary_by_http_activity(),
2261
vary_by_http_protocol_version(),
2265
super(TestActivity, self).setUp()
2266
TestActivityMixin.setUp(self)
2269
class TestNoReportActivity(tests.TestCase, TestActivityMixin):
2271
# Unlike TestActivity, we are really testing ReportingFileSocket and
2272
# ReportingSocket, so we don't need all the parametrization. Since
2273
# ReportingFileSocket and ReportingSocket are wrappers, it's easier to
2274
# test them through their use by the transport than directly (that's a
2275
# bit less clean but far more simpler and effective).
2276
_activity_server = ActivityHTTPServer
2277
_protocol_version = 'HTTP/1.1'
2280
super(TestNoReportActivity, self).setUp()
2281
self._transport =_urllib.HttpTransport_urllib
2282
TestActivityMixin.setUp(self)
2284
def assertActivitiesMatch(self):
2285
# Nothing to check here
2289
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
2290
"""Test authentication on the redirected http server."""
2292
scenarios = vary_by_http_protocol_version()
2294
_auth_header = 'Authorization'
2295
_password_prompt_prefix = ''
2296
_username_prompt_prefix = ''
2297
_auth_server = http_utils.HTTPBasicAuthServer
2298
_transport = _urllib.HttpTransport_urllib
2301
super(TestAuthOnRedirected, self).setUp()
2302
self.build_tree_contents([('a','a'),
2304
('1/a', 'redirected once'),
2306
new_prefix = 'http://%s:%s' % (self.new_server.host,
2307
self.new_server.port)
2308
self.old_server.redirections = [
2309
('(.*)', r'%s/1\1' % (new_prefix), 301),]
2310
self.old_transport = self.get_old_transport()
2311
self.new_server.add_user('joe', 'foo')
2312
cleanup_http_redirection_connections(self)
2314
def create_transport_readonly_server(self):
2315
server = self._auth_server(protocol_version=self._protocol_version)
2316
server._url_protocol = self._url_protocol
2322
def test_auth_on_redirected_via_do_catching_redirections(self):
2323
self.redirections = 0
2325
def redirected(t, exception, redirection_notice):
2326
self.redirections += 1
2327
redirected_t = t._redirected_to(exception.source, exception.target)
2328
self.addCleanup(redirected_t.disconnect)
2331
stdout = tests.StringIOWrapper()
2332
stderr = tests.StringIOWrapper()
2333
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
2334
stdout=stdout, stderr=stderr)
2335
self.assertEqual('redirected once',
2336
transport.do_catching_redirections(
2337
self.get_a, self.old_transport, redirected).read())
2338
self.assertEqual(1, self.redirections)
2339
# stdin should be empty
2340
self.assertEqual('', ui.ui_factory.stdin.readline())
2341
# stdout should be empty, stderr will contains the prompts
2342
self.assertEqual('', stdout.getvalue())
2344
def test_auth_on_redirected_via_following_redirections(self):
2345
self.new_server.add_user('joe', 'foo')
2346
stdout = tests.StringIOWrapper()
2347
stderr = tests.StringIOWrapper()
2348
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
2349
stdout=stdout, stderr=stderr)
2350
t = self.old_transport
2351
req = RedirectedRequest('GET', t.abspath('a'))
2352
new_prefix = 'http://%s:%s' % (self.new_server.host,
2353
self.new_server.port)
2354
self.old_server.redirections = [
2355
('(.*)', r'%s/1\1' % (new_prefix), 301),]
2356
self.assertEqual('redirected once', t._perform(req).read())
2357
# stdin should be empty
2358
self.assertEqual('', ui.ui_factory.stdin.readline())
2359
# stdout should be empty, stderr will contains the prompts
2360
self.assertEqual('', stdout.getvalue())
1385
def get_user_transport(self, user=None, password=None):
1386
self._install_env({'all_proxy': self.get_user_url(user, password)})
1387
return self._transport(self.server.get_url())
1389
def _install_env(self, env):
1390
for name, value in env.iteritems():
1391
self._old_env[name] = osutils.set_or_unset_env(name, value)
1393
def _restore_env(self):
1394
for name, value in self._old_env.iteritems():
1395
osutils.set_or_unset_env(name, value)
1398
class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
1399
"""Test http basic authentication scheme"""
1401
_transport = HttpTransport_urllib
1403
def create_transport_readonly_server(self):
1404
return HTTPBasicAuthServer()
1407
class TestHTTPProxyBasicAuth(TestProxyAuth, TestCaseWithWebserver):
1408
"""Test proxy basic authentication scheme"""
1410
_transport = HttpTransport_urllib
1412
def create_transport_readonly_server(self):
1413
return ProxyBasicAuthServer()
1416
class TestDigestAuth(object):
1417
"""Digest Authentication specific tests"""
1419
def test_changing_nonce(self):
1420
self.server.add_user('joe', 'foo')
1421
t = self.get_user_transport('joe', 'foo')
1422
self.assertEqual('contents of a\n', t.get('a').read())
1423
self.assertEqual('contents of b\n', t.get('b').read())
1424
# Only one 'Authentication Required' error should have
1426
self.assertEqual(1, self.server.auth_required_errors)
1427
# The server invalidates the current nonce
1428
self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1429
self.assertEqual('contents of a\n', t.get('a').read())
1430
# Two 'Authentication Required' errors should occur (the
1431
# initial 'who are you' and a second 'who are you' with the new nonce)
1432
self.assertEqual(2, self.server.auth_required_errors)
1435
class TestHTTPDigestAuth(TestHTTPAuth, TestDigestAuth, TestCaseWithWebserver):
1436
"""Test http digest authentication scheme"""
1438
_transport = HttpTransport_urllib
1440
def create_transport_readonly_server(self):
1441
return HTTPDigestAuthServer()
1444
class TestHTTPProxyDigestAuth(TestProxyAuth, TestDigestAuth,
1445
TestCaseWithWebserver):
1446
"""Test proxy digest authentication scheme"""
1448
_transport = HttpTransport_urllib
1450
def create_transport_readonly_server(self):
1451
return ProxyDigestAuthServer()