1
# Copyright (C) 2005-2012, 2015, 2016, 2017 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for HTTP implementations.
19
This module defines a load_tests() method that parametrize tests classes for
20
transport implementation, http protocol versions and authentication schemes.
23
# TODO: Should be renamed to breezy.transport.http.tests?
24
# TODO: What about renaming to breezy.tests.transport.http ?
26
from http.client import UnknownProtocol, parse_headers
27
from http.server import SimpleHTTPRequestHandler
47
remote as _mod_remote,
55
from .scenarios import (
56
load_tests_apply_scenarios,
59
from ..transport import (
63
from ..transport.http import (
68
load_tests = load_tests_apply_scenarios
71
def vary_by_http_client_implementation():
72
"""Test the libraries we can use, currently just urllib."""
73
transport_scenarios = [
74
('urllib', dict(_transport=HttpTransport,
75
_server=http_server.HttpServer,
76
_url_protocol='http',)),
78
return transport_scenarios
81
def vary_by_http_protocol_version():
82
"""Test on http/1.0 and 1.1"""
84
('HTTP/1.0', dict(_protocol_version='HTTP/1.0')),
85
('HTTP/1.1', dict(_protocol_version='HTTP/1.1')),
89
def vary_by_http_auth_scheme():
91
('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
92
('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
94
dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
96
# Add some attributes common to all scenarios
97
for scenario_id, scenario_dict in scenarios:
98
scenario_dict.update(_auth_header='Authorization',
99
_username_prompt_prefix='',
100
_password_prompt_prefix='')
104
def vary_by_http_proxy_auth_scheme():
106
('proxy-basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
107
('proxy-digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
108
('proxy-basicdigest',
109
dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
111
# Add some attributes common to all scenarios
112
for scenario_id, scenario_dict in scenarios:
113
scenario_dict.update(_auth_header='Proxy-Authorization',
114
_username_prompt_prefix='Proxy ',
115
_password_prompt_prefix='Proxy ')
119
def vary_by_http_activity():
120
activity_scenarios = [
121
('urllib,http', dict(_activity_server=ActivityHTTPServer,
122
_transport=HttpTransport,)),
124
if features.HTTPSServerFeature.available():
125
# FIXME: Until we have a better way to handle self-signed certificates
126
# (like allowing them in a test specific authentication.conf for
127
# example), we need some specialized urllib transport for tests.
133
class HTTPS_transport(HttpTransport):
135
def __init__(self, base, _from_transport=None):
136
super(HTTPS_transport, self).__init__(
137
base, _from_transport=_from_transport,
138
ca_certs=ssl_certs.build_path('ca.crt'))
140
activity_scenarios.append(
141
('urllib,https', dict(_activity_server=ActivityHTTPSServer,
142
_transport=HTTPS_transport,)),)
143
return activity_scenarios
146
class FakeManager(object):
149
self.credentials = []
151
def add_password(self, realm, host, username, password):
152
self.credentials.append([realm, host, username, password])
155
class RecordingServer(object):
156
"""A fake HTTP server.
158
It records the bytes sent to it, and replies with a 200.
161
def __init__(self, expect_body_tail=None, scheme=''):
164
:type expect_body_tail: str
165
:param expect_body_tail: a reply won't be sent until this string is
168
self._expect_body_tail = expect_body_tail
171
self.received_bytes = b''
175
return '%s://%s:%s/' % (self.scheme, self.host, self.port)
177
def start_server(self):
178
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
179
self._sock.bind(('127.0.0.1', 0))
180
self.host, self.port = self._sock.getsockname()
181
self._ready = threading.Event()
182
self._thread = test_server.TestThread(
183
sync_event=self._ready, target=self._accept_read_and_reply)
185
if 'threads' in tests.selftest_debug_flags:
186
sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
189
def _accept_read_and_reply(self):
192
conn, address = self._sock.accept()
193
if self._expect_body_tail is not None:
194
while not self.received_bytes.endswith(self._expect_body_tail):
195
self.received_bytes += conn.recv(4096)
196
conn.sendall(b'HTTP/1.1 200 OK\r\n')
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))
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 = http.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_build_basic_header_with_long_creds(self):
244
handler = http.BasicAuthHandler()
245
user = 'user' * 10 # length 40
246
password = 'password' * 5 # length 40
247
header = handler.build_auth_header(
248
dict(user=user, password=password), None)
249
# https://bugs.launchpad.net/bzr/+bug/1606203 was caused by incorrectly
250
# creating a header value with an embedded '\n'
251
self.assertFalse('\n' in header)
253
def test_basic_extract_realm(self):
254
scheme, remainder = self.parse_header(
255
'Basic realm="Thou should not pass"',
256
http.BasicAuthHandler)
257
match, realm = self.auth_handler.extract_realm(remainder)
258
self.assertTrue(match is not None)
259
self.assertEqual(u'Thou should not pass', realm)
261
def test_digest_header(self):
262
scheme, remainder = self.parse_header(
263
'Digest realm="Thou should not pass"')
264
self.assertEqual('digest', scheme)
265
self.assertEqual('realm="Thou should not pass"', remainder)
268
class TestHTTPRangeParsing(tests.TestCase):
271
super(TestHTTPRangeParsing, self).setUp()
272
# We focus on range parsing here and ignore everything else
274
class RequestHandler(http_server.TestingHTTPRequestHandler):
275
def setup(self): pass
277
def handle(self): pass
279
def finish(self): pass
281
self.req_handler = RequestHandler(None, None, None)
283
def assertRanges(self, ranges, header, file_size):
284
self.assertEqual(ranges,
285
self.req_handler._parse_ranges(header, file_size))
287
def test_simple_range(self):
288
self.assertRanges([(0, 2)], 'bytes=0-2', 12)
291
self.assertRanges([(8, 11)], 'bytes=-4', 12)
293
def test_tail_bigger_than_file(self):
294
self.assertRanges([(0, 11)], 'bytes=-99', 12)
296
def test_range_without_end(self):
297
self.assertRanges([(4, 11)], 'bytes=4-', 12)
299
def test_invalid_ranges(self):
300
self.assertRanges(None, 'bytes=12-22', 12)
301
self.assertRanges(None, 'bytes=1-3,12-22', 12)
302
self.assertRanges(None, 'bytes=-', 12)
305
class TestHTTPServer(tests.TestCase):
306
"""Test the HTTP servers implementations."""
308
def test_invalid_protocol(self):
309
class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
311
protocol_version = 'HTTP/0.1'
313
self.assertRaises(UnknownProtocol,
314
http_server.HttpServer, BogusRequestHandler)
316
def test_force_invalid_protocol(self):
317
self.assertRaises(UnknownProtocol,
318
http_server.HttpServer, protocol_version='HTTP/0.1')
320
def test_server_start_and_stop(self):
321
server = http_server.HttpServer()
322
self.addCleanup(server.stop_server)
323
server.start_server()
324
self.assertTrue(server.server is not None)
325
self.assertTrue(server.server.serving is not None)
326
self.assertTrue(server.server.serving)
328
def test_create_http_server_one_zero(self):
329
class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
331
protocol_version = 'HTTP/1.0'
333
server = http_server.HttpServer(RequestHandlerOneZero)
334
self.start_server(server)
335
self.assertIsInstance(server.server, http_server.TestingHTTPServer)
337
def test_create_http_server_one_one(self):
338
class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
340
protocol_version = 'HTTP/1.1'
342
server = http_server.HttpServer(RequestHandlerOneOne)
343
self.start_server(server)
344
self.assertIsInstance(server.server,
345
http_server.TestingThreadingHTTPServer)
347
def test_create_http_server_force_one_one(self):
348
class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
350
protocol_version = 'HTTP/1.0'
352
server = http_server.HttpServer(RequestHandlerOneZero,
353
protocol_version='HTTP/1.1')
354
self.start_server(server)
355
self.assertIsInstance(server.server,
356
http_server.TestingThreadingHTTPServer)
358
def test_create_http_server_force_one_zero(self):
359
class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
361
protocol_version = 'HTTP/1.1'
363
server = http_server.HttpServer(RequestHandlerOneOne,
364
protocol_version='HTTP/1.0')
365
self.start_server(server)
366
self.assertIsInstance(server.server,
367
http_server.TestingHTTPServer)
370
class TestHttpTransportUrls(tests.TestCase):
371
"""Test the http urls."""
373
scenarios = vary_by_http_client_implementation()
375
def test_abs_url(self):
376
"""Construction of absolute http URLs"""
377
t = self._transport('http://example.com/bzr/bzr.dev/')
378
eq = self.assertEqualDiff
379
eq(t.abspath('.'), 'http://example.com/bzr/bzr.dev')
380
eq(t.abspath('foo/bar'), 'http://example.com/bzr/bzr.dev/foo/bar')
381
eq(t.abspath('.bzr'), 'http://example.com/bzr/bzr.dev/.bzr')
382
eq(t.abspath('.bzr/1//2/./3'),
383
'http://example.com/bzr/bzr.dev/.bzr/1/2/3')
385
def test_invalid_http_urls(self):
386
"""Trap invalid construction of urls"""
387
self._transport('http://example.com/bzr/bzr.dev/')
388
self.assertRaises(urlutils.InvalidURL,
390
'http://example.com:port/bzr/bzr.dev/')
392
def test_http_root_urls(self):
393
"""Construction of URLs from server root"""
394
t = self._transport('http://example.com/')
395
eq = self.assertEqualDiff
396
eq(t.abspath('.bzr/tree-version'),
397
'http://example.com/.bzr/tree-version')
399
def test_http_impl_urls(self):
400
"""There are servers which ask for particular clients to connect"""
401
server = self._server()
402
server.start_server()
404
url = server.get_url()
405
self.assertTrue(url.startswith('%s://' % self._url_protocol))
410
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
411
"""Test the http connections."""
413
scenarios = multiply_scenarios(
414
vary_by_http_client_implementation(),
415
vary_by_http_protocol_version(),
419
super(TestHTTPConnections, self).setUp()
420
self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
421
transport=self.get_transport())
423
def test_http_has(self):
424
server = self.get_readonly_server()
425
t = self.get_readonly_transport()
426
self.assertEqual(t.has('foo/bar'), True)
427
self.assertEqual(len(server.logs), 1)
428
self.assertContainsRe(server.logs[0],
429
r'"HEAD /foo/bar HTTP/1.." (200|302) - "-" "Breezy/')
431
def test_http_has_not_found(self):
432
server = self.get_readonly_server()
433
t = self.get_readonly_transport()
434
self.assertEqual(t.has('not-found'), False)
435
self.assertContainsRe(server.logs[1],
436
r'"HEAD /not-found HTTP/1.." 404 - "-" "Breezy/')
438
def test_http_get(self):
439
server = self.get_readonly_server()
440
t = self.get_readonly_transport()
441
fp = t.get('foo/bar')
442
self.assertEqualDiff(
444
b'contents of foo/bar\n')
445
self.assertEqual(len(server.logs), 1)
446
self.assertTrue(server.logs[0].find(
447
'"GET /foo/bar HTTP/1.1" 200 - "-" "Breezy/%s'
448
% breezy.__version__) > -1)
450
def test_has_on_bogus_host(self):
451
# Get a free address and don't 'accept' on it, so that we
452
# can be sure there is no http handler there, but set a
453
# reasonable timeout to not slow down tests too much.
454
default_timeout = socket.getdefaulttimeout()
456
socket.setdefaulttimeout(2)
458
s.bind(('localhost', 0))
459
t = self._transport('http://%s:%s/' % s.getsockname())
460
self.assertRaises(errors.ConnectionError, t.has, 'foo/bar')
462
socket.setdefaulttimeout(default_timeout)
465
class TestHttpTransportRegistration(tests.TestCase):
466
"""Test registrations of various http implementations"""
468
scenarios = vary_by_http_client_implementation()
470
def test_http_registered(self):
471
t = transport.get_transport_from_url(
472
'%s://foo.com/' % self._url_protocol)
473
self.assertIsInstance(t, transport.Transport)
474
self.assertIsInstance(t, self._transport)
477
class TestPost(tests.TestCase):
479
scenarios = multiply_scenarios(
480
vary_by_http_client_implementation(),
481
vary_by_http_protocol_version(),
484
def test_post_body_is_received(self):
485
server = RecordingServer(expect_body_tail=b'end-of-body',
486
scheme=self._url_protocol)
487
self.start_server(server)
488
url = server.get_url()
489
# FIXME: needs a cleanup -- vila 20100611
490
http_transport = transport.get_transport_from_url(url)
491
code, response = http_transport._post(b'abc def end-of-body')
493
server.received_bytes.startswith(b'POST /.bzr/smart HTTP/1.'))
495
b'content-length: 19\r' in server.received_bytes.lower())
496
self.assertTrue(b'content-type: application/octet-stream\r'
497
in server.received_bytes.lower())
498
# The transport should not be assuming that the server can accept
499
# chunked encoding the first time it connects, because HTTP/1.1, so we
500
# check for the literal string.
502
server.received_bytes.endswith(b'\r\n\r\nabc def end-of-body'))
505
class TestRangeHeader(tests.TestCase):
506
"""Test range_header method"""
508
def check_header(self, value, ranges=[], tail=0):
509
offsets = [(start, end - start + 1) for start, end in ranges]
510
coalesce = transport.Transport._coalesce_offsets
511
coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
512
range_header = http.HttpTransport._range_header
513
self.assertEqual(value, range_header(coalesced, tail))
515
def test_range_header_single(self):
516
self.check_header('0-9', ranges=[(0, 9)])
517
self.check_header('100-109', ranges=[(100, 109)])
519
def test_range_header_tail(self):
520
self.check_header('-10', tail=10)
521
self.check_header('-50', tail=50)
523
def test_range_header_multi(self):
524
self.check_header('0-9,100-200,300-5000',
525
ranges=[(0, 9), (100, 200), (300, 5000)])
527
def test_range_header_mixed(self):
528
self.check_header('0-9,300-5000,-50',
529
ranges=[(0, 9), (300, 5000)],
533
class TestSpecificRequestHandler(http_utils.TestCaseWithWebserver):
534
"""Tests a specific request handler.
536
Daughter classes are expected to override _req_handler_class
539
scenarios = multiply_scenarios(
540
vary_by_http_client_implementation(),
541
vary_by_http_protocol_version(),
544
# Provide a useful default
545
_req_handler_class = http_server.TestingHTTPRequestHandler
547
def create_transport_readonly_server(self):
548
server = http_server.HttpServer(self._req_handler_class,
549
protocol_version=self._protocol_version)
550
server._url_protocol = self._url_protocol
554
class WallRequestHandler(http_server.TestingHTTPRequestHandler):
555
"""Whatever request comes in, close the connection"""
557
def _handle_one_request(self):
558
"""Handle a single HTTP request, by abruptly closing the connection"""
559
self.close_connection = 1
562
class TestWallServer(TestSpecificRequestHandler):
563
"""Tests exceptions during the connection phase"""
565
_req_handler_class = WallRequestHandler
567
def test_http_has(self):
568
t = self.get_readonly_transport()
569
# Unfortunately httplib (see HTTPResponse._read_status
570
# for details) make no distinction between a closed
571
# socket and badly formatted status line, so we can't
572
# just test for ConnectionError, we have to test
573
# InvalidHttpResponse too.
574
self.assertRaises((errors.ConnectionError,
575
errors.InvalidHttpResponse),
578
def test_http_get(self):
579
t = self.get_readonly_transport()
580
self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
581
errors.InvalidHttpResponse),
585
class BadStatusRequestHandler(http_server.TestingHTTPRequestHandler):
586
"""Whatever request comes in, returns a bad status"""
588
def parse_request(self):
589
"""Fakes handling a single HTTP request, returns a bad status"""
590
ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
591
self.send_response(0, "Bad status")
592
self.close_connection = 1
596
class TestBadStatusServer(TestSpecificRequestHandler):
597
"""Tests bad status from server."""
599
_req_handler_class = BadStatusRequestHandler
602
super(TestBadStatusServer, self).setUp()
603
# See https://bugs.launchpad.net/bzr/+bug/1451448 for details.
604
# TD;LR: Running both a TCP client and server in the same process and
605
# thread uncovers a race in python. The fix is to run the server in a
606
# different process. Trying to fix yet another race here is not worth
607
# the effort. -- vila 2015-09-06
608
if 'HTTP/1.0' in self.id():
609
raise tests.TestSkipped(
610
'Client/Server in the same process and thread can hang')
612
def test_http_has(self):
613
t = self.get_readonly_transport()
614
self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
615
errors.InvalidHttpResponse),
618
def test_http_get(self):
619
t = self.get_readonly_transport()
620
self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
621
errors.InvalidHttpResponse),
625
class InvalidStatusRequestHandler(http_server.TestingHTTPRequestHandler):
626
"""Whatever request comes in, returns an invalid status"""
628
def parse_request(self):
629
"""Fakes handling a single HTTP request, returns a bad status"""
630
ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
631
self.wfile.write(b"Invalid status line\r\n")
632
# If we don't close the connection pycurl will hang. Since this is a
633
# stress test we don't *have* to respect the protocol, but we don't
634
# have to sabotage it too much either.
635
self.close_connection = True
639
class TestInvalidStatusServer(TestBadStatusServer):
640
"""Tests invalid status from server.
642
Both implementations raises the same error as for a bad status.
645
_req_handler_class = InvalidStatusRequestHandler
648
class BadProtocolRequestHandler(http_server.TestingHTTPRequestHandler):
649
"""Whatever request comes in, returns a bad protocol version"""
651
def parse_request(self):
652
"""Fakes handling a single HTTP request, returns a bad status"""
653
ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
654
# Returns an invalid protocol version, but curl just
655
# ignores it and those cannot be tested.
656
self.wfile.write(b"%s %d %s\r\n" % (
657
b'HTTP/0.0', 404, b'Look at my protocol version'))
661
class TestBadProtocolServer(TestSpecificRequestHandler):
662
"""Tests bad protocol from server."""
664
_req_handler_class = BadProtocolRequestHandler
666
def test_http_has(self):
667
t = self.get_readonly_transport()
668
self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
670
def test_http_get(self):
671
t = self.get_readonly_transport()
672
self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
675
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
676
"""Whatever request comes in, returns a 403 code"""
678
def parse_request(self):
679
"""Handle a single HTTP request, by replying we cannot handle it"""
680
ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
685
class TestForbiddenServer(TestSpecificRequestHandler):
686
"""Tests forbidden server"""
688
_req_handler_class = ForbiddenRequestHandler
690
def test_http_has(self):
691
t = self.get_readonly_transport()
692
self.assertRaises(errors.TransportError, t.has, 'foo/bar')
694
def test_http_get(self):
695
t = self.get_readonly_transport()
696
self.assertRaises(errors.TransportError, t.get, 'foo/bar')
699
class TestRecordingServer(tests.TestCase):
701
def test_create(self):
702
server = RecordingServer(expect_body_tail=None)
703
self.assertEqual(b'', server.received_bytes)
704
self.assertEqual(None, server.host)
705
self.assertEqual(None, server.port)
707
def test_setUp_and_stop(self):
708
server = RecordingServer(expect_body_tail=None)
709
server.start_server()
711
self.assertNotEqual(None, server.host)
712
self.assertNotEqual(None, server.port)
715
self.assertEqual(None, server.host)
716
self.assertEqual(None, server.port)
718
def test_send_receive_bytes(self):
719
server = RecordingServer(expect_body_tail=b'c', scheme='http')
720
self.start_server(server)
721
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
722
sock.connect((server.host, server.port))
724
self.assertEqual(b'HTTP/1.1 200 OK\r\n',
725
osutils.recv_all(sock, 4096))
726
self.assertEqual(b'abc', server.received_bytes)
729
class TestRangeRequestServer(TestSpecificRequestHandler):
730
"""Tests readv requests against server.
732
We test against default "normal" server.
736
super(TestRangeRequestServer, self).setUp()
737
self.build_tree_contents([('a', b'0123456789')],)
739
def test_readv(self):
740
t = self.get_readonly_transport()
741
l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
742
self.assertEqual(l[0], (0, b'0'))
743
self.assertEqual(l[1], (1, b'1'))
744
self.assertEqual(l[2], (3, b'34'))
745
self.assertEqual(l[3], (9, b'9'))
747
def test_readv_out_of_order(self):
748
t = self.get_readonly_transport()
749
l = list(t.readv('a', ((1, 1), (9, 1), (0, 1), (3, 2))))
750
self.assertEqual(l[0], (1, b'1'))
751
self.assertEqual(l[1], (9, b'9'))
752
self.assertEqual(l[2], (0, b'0'))
753
self.assertEqual(l[3], (3, b'34'))
755
def test_readv_invalid_ranges(self):
756
t = self.get_readonly_transport()
758
# This is intentionally reading off the end of the file
759
# since we are sure that it cannot get there
760
self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
761
t.readv, 'a', [(1, 1), (8, 10)])
763
# This is trying to seek past the end of the file, it should
764
# also raise a special error
765
self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
766
t.readv, 'a', [(12, 2)])
768
def test_readv_multiple_get_requests(self):
769
server = self.get_readonly_server()
770
t = self.get_readonly_transport()
771
# force transport to issue multiple requests
772
t._max_readv_combine = 1
773
t._max_get_ranges = 1
774
l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
775
self.assertEqual(l[0], (0, b'0'))
776
self.assertEqual(l[1], (1, b'1'))
777
self.assertEqual(l[2], (3, b'34'))
778
self.assertEqual(l[3], (9, b'9'))
779
# The server should have issued 4 requests
780
self.assertEqual(4, server.GET_request_nb)
782
def test_readv_get_max_size(self):
783
server = self.get_readonly_server()
784
t = self.get_readonly_transport()
785
# force transport to issue multiple requests by limiting the number of
786
# bytes by request. Note that this apply to coalesced offsets only, a
787
# single range will keep its size even if bigger than the limit.
789
l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
790
self.assertEqual(l[0], (0, b'0'))
791
self.assertEqual(l[1], (1, b'1'))
792
self.assertEqual(l[2], (2, b'2345'))
793
self.assertEqual(l[3], (6, b'6789'))
794
# The server should have issued 3 requests
795
self.assertEqual(3, server.GET_request_nb)
797
def test_complete_readv_leave_pipe_clean(self):
798
server = self.get_readonly_server()
799
t = self.get_readonly_transport()
800
# force transport to issue multiple requests
802
list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
803
# The server should have issued 3 requests
804
self.assertEqual(3, server.GET_request_nb)
805
self.assertEqual(b'0123456789', t.get_bytes('a'))
806
self.assertEqual(4, server.GET_request_nb)
808
def test_incomplete_readv_leave_pipe_clean(self):
809
server = self.get_readonly_server()
810
t = self.get_readonly_transport()
811
# force transport to issue multiple requests
813
# Don't collapse readv results into a list so that we leave unread
814
# bytes on the socket
815
ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
816
self.assertEqual((0, b'0'), next(ireadv))
817
# The server should have issued one request so far
818
self.assertEqual(1, server.GET_request_nb)
819
self.assertEqual(b'0123456789', t.get_bytes('a'))
820
# get_bytes issued an additional request, the readv pending ones are
822
self.assertEqual(2, server.GET_request_nb)
825
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
826
"""Always reply to range request as if they were single.
828
Don't be explicit about it, just to annoy the clients.
831
def get_multiple_ranges(self, file, file_size, ranges):
832
"""Answer as if it was a single range request and ignores the rest"""
833
(start, end) = ranges[0]
834
return self.get_single_range(file, file_size, start, end)
837
class TestSingleRangeRequestServer(TestRangeRequestServer):
838
"""Test readv against a server which accept only single range requests"""
840
_req_handler_class = SingleRangeRequestHandler
843
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
844
"""Only reply to simple range requests, errors out on multiple"""
846
def get_multiple_ranges(self, file, file_size, ranges):
847
"""Refuses the multiple ranges request"""
850
self.send_error(416, "Requested range not satisfiable")
852
(start, end) = ranges[0]
853
return self.get_single_range(file, file_size, start, end)
856
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
857
"""Test readv against a server which only accept single range requests"""
859
_req_handler_class = SingleOnlyRangeRequestHandler
862
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
863
"""Ignore range requests without notice"""
866
# Update the statistics
867
self.server.test_case_server.GET_request_nb += 1
868
# Just bypass the range handling done by TestingHTTPRequestHandler
869
return SimpleHTTPRequestHandler.do_GET(self)
872
class TestNoRangeRequestServer(TestRangeRequestServer):
873
"""Test readv against a server which do not accept range requests"""
875
_req_handler_class = NoRangeRequestHandler
878
class MultipleRangeWithoutContentLengthRequestHandler(
879
http_server.TestingHTTPRequestHandler):
880
"""Reply to multiple range requests without content length header."""
882
def get_multiple_ranges(self, file, file_size, ranges):
883
self.send_response(206)
884
self.send_header('Accept-Ranges', 'bytes')
885
# XXX: this is strange; the 'random' name below seems undefined and
886
# yet the tests pass -- mbp 2010-10-11 bug 658773
887
boundary = "%d" % random.randint(0, 0x7FFFFFFF)
888
self.send_header("Content-Type",
889
"multipart/byteranges; boundary=%s" % boundary)
891
for (start, end) in ranges:
892
self.wfile.write(b"--%s\r\n" % boundary.encode('ascii'))
893
self.send_header("Content-type", 'application/octet-stream')
894
self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
898
self.send_range_content(file, start, end - start + 1)
900
self.wfile.write(b"--%s\r\n" % boundary)
903
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
905
_req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
908
class TruncatedMultipleRangeRequestHandler(
909
http_server.TestingHTTPRequestHandler):
910
"""Reply to multiple range requests truncating the last ones.
912
This server generates responses whose Content-Length describes all the
913
ranges, but fail to include the last ones leading to client short reads.
914
This has been observed randomly with lighttpd (bug #179368).
917
_truncated_ranges = 2
919
def get_multiple_ranges(self, file, file_size, ranges):
920
self.send_response(206)
921
self.send_header('Accept-Ranges', 'bytes')
923
self.send_header('Content-Type',
924
'multipart/byteranges; boundary=%s' % boundary)
925
boundary_line = b'--%s\r\n' % boundary.encode('ascii')
926
# Calculate the Content-Length
928
for (start, end) in ranges:
929
content_length += len(boundary_line)
930
content_length += self._header_line_length(
931
'Content-type', 'application/octet-stream')
932
content_length += self._header_line_length(
933
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
934
content_length += len('\r\n') # end headers
935
content_length += end - start # + 1
936
content_length += len(boundary_line)
937
self.send_header('Content-length', content_length)
940
# Send the multipart body
942
for (start, end) in ranges:
943
self.wfile.write(boundary_line)
944
self.send_header('Content-type', 'application/octet-stream')
945
self.send_header('Content-Range', 'bytes %d-%d/%d'
946
% (start, end, file_size))
948
if cur + self._truncated_ranges >= len(ranges):
949
# Abruptly ends the response and close the connection
950
self.close_connection = 1
952
self.send_range_content(file, start, end - start + 1)
955
self.wfile.write(boundary_line)
958
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
960
_req_handler_class = TruncatedMultipleRangeRequestHandler
963
super(TestTruncatedMultipleRangeServer, self).setUp()
964
self.build_tree_contents([('a', b'0123456789')],)
966
def test_readv_with_short_reads(self):
967
server = self.get_readonly_server()
968
t = self.get_readonly_transport()
969
# Force separate ranges for each offset
970
t._bytes_to_read_before_seek = 0
971
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
972
self.assertEqual((0, b'0'), next(ireadv))
973
self.assertEqual((2, b'2'), next(ireadv))
974
# Only one request have been issued so far
975
self.assertEqual(1, server.GET_request_nb)
976
self.assertEqual((4, b'45'), next(ireadv))
977
self.assertEqual((9, b'9'), next(ireadv))
978
# We issue 3 requests: two multiple (4 ranges, then 2 ranges) then a
980
self.assertEqual(3, server.GET_request_nb)
981
# Finally the client have tried a single range request and stays in
983
self.assertEqual('single', t._range_hint)
986
class TruncatedBeforeBoundaryRequestHandler(
987
http_server.TestingHTTPRequestHandler):
988
"""Truncation before a boundary, like in bug 198646"""
990
_truncated_ranges = 1
992
def get_multiple_ranges(self, file, file_size, ranges):
993
self.send_response(206)
994
self.send_header('Accept-Ranges', 'bytes')
996
self.send_header('Content-Type',
997
'multipart/byteranges; boundary=%s' % boundary)
998
boundary_line = b'--%s\r\n' % boundary.encode('ascii')
999
# Calculate the Content-Length
1001
for (start, end) in ranges:
1002
content_length += len(boundary_line)
1003
content_length += self._header_line_length(
1004
'Content-type', 'application/octet-stream')
1005
content_length += self._header_line_length(
1006
'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
1007
content_length += len('\r\n') # end headers
1008
content_length += end - start # + 1
1009
content_length += len(boundary_line)
1010
self.send_header('Content-length', content_length)
1013
# Send the multipart body
1015
for (start, end) in ranges:
1016
if cur + self._truncated_ranges >= len(ranges):
1017
# Abruptly ends the response and close the connection
1018
self.close_connection = 1
1020
self.wfile.write(boundary_line)
1021
self.send_header('Content-type', 'application/octet-stream')
1022
self.send_header('Content-Range', 'bytes %d-%d/%d'
1023
% (start, end, file_size))
1025
self.send_range_content(file, start, end - start + 1)
1028
self.wfile.write(boundary_line)
1031
class TestTruncatedBeforeBoundary(TestSpecificRequestHandler):
1032
"""Tests the case of bug 198646, disconnecting before a boundary."""
1034
_req_handler_class = TruncatedBeforeBoundaryRequestHandler
1037
super(TestTruncatedBeforeBoundary, self).setUp()
1038
self.build_tree_contents([('a', b'0123456789')],)
1040
def test_readv_with_short_reads(self):
1041
server = self.get_readonly_server()
1042
t = self.get_readonly_transport()
1043
# Force separate ranges for each offset
1044
t._bytes_to_read_before_seek = 0
1045
ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1046
self.assertEqual((0, b'0'), next(ireadv))
1047
self.assertEqual((2, b'2'), next(ireadv))
1048
self.assertEqual((4, b'45'), next(ireadv))
1049
self.assertEqual((9, b'9'), next(ireadv))
1052
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1053
"""Errors out when range specifiers exceed the limit"""
1055
def get_multiple_ranges(self, file, file_size, ranges):
1056
"""Refuses the multiple ranges request"""
1057
tcs = self.server.test_case_server
1058
if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
1060
# Emulate apache behavior
1061
self.send_error(400, "Bad Request")
1063
return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
1064
self, file, file_size, ranges)
1067
class LimitedRangeHTTPServer(http_server.HttpServer):
1068
"""An HttpServer erroring out on requests with too much range specifiers"""
1070
def __init__(self, request_handler=LimitedRangeRequestHandler,
1071
protocol_version=None,
1073
http_server.HttpServer.__init__(self, request_handler,
1074
protocol_version=protocol_version)
1075
self.range_limit = range_limit
1078
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1079
"""Tests readv requests against a server erroring out on too much ranges."""
1081
scenarios = multiply_scenarios(
1082
vary_by_http_client_implementation(),
1083
vary_by_http_protocol_version(),
1086
# Requests with more range specifiers will error out
1089
def create_transport_readonly_server(self):
1090
return LimitedRangeHTTPServer(range_limit=self.range_limit,
1091
protocol_version=self._protocol_version)
1094
super(TestLimitedRangeRequestServer, self).setUp()
1095
# We need to manipulate ranges that correspond to real chunks in the
1096
# response, so we build a content appropriately.
1097
filler = b''.join([b'abcdefghij' for x in range(102)])
1098
content = b''.join([b'%04d' % v + filler for v in range(16)])
1099
self.build_tree_contents([('a', content)],)
1101
def test_few_ranges(self):
1102
t = self.get_readonly_transport()
1103
l = list(t.readv('a', ((0, 4), (1024, 4), )))
1104
self.assertEqual(l[0], (0, b'0000'))
1105
self.assertEqual(l[1], (1024, b'0001'))
1106
self.assertEqual(1, self.get_readonly_server().GET_request_nb)
1108
def test_more_ranges(self):
1109
t = self.get_readonly_transport()
1110
l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
1111
self.assertEqual(l[0], (0, b'0000'))
1112
self.assertEqual(l[1], (1024, b'0001'))
1113
self.assertEqual(l[2], (4096, b'0004'))
1114
self.assertEqual(l[3], (8192, b'0008'))
1115
# The server will refuse to serve the first request (too much ranges),
1116
# a second request will succeed.
1117
self.assertEqual(2, self.get_readonly_server().GET_request_nb)
1120
class TestHttpProxyWhiteBox(tests.TestCase):
1121
"""Whitebox test proxy http authorization.
1123
Only the urllib implementation is tested here.
1126
def _proxied_request(self):
1127
handler = http.ProxyHandler()
1128
request = http.Request('GET', 'http://baz/buzzle')
1129
handler.set_proxy(request, 'http')
1132
def assertEvaluateProxyBypass(self, expected, host, no_proxy):
1133
handler = http.ProxyHandler()
1134
self.assertEqual(expected,
1135
handler.evaluate_proxy_bypass(host, no_proxy))
1137
def test_empty_user(self):
1138
self.overrideEnv('http_proxy', 'http://bar.com')
1139
request = self._proxied_request()
1140
self.assertFalse('Proxy-authorization' in request.headers)
1142
def test_user_with_at(self):
1143
self.overrideEnv('http_proxy',
1144
'http://username@domain:password@proxy_host:1234')
1145
request = self._proxied_request()
1146
self.assertFalse('Proxy-authorization' in request.headers)
1148
def test_invalid_proxy(self):
1149
"""A proxy env variable without scheme"""
1150
self.overrideEnv('http_proxy', 'host:1234')
1151
self.assertRaises(urlutils.InvalidURL, self._proxied_request)
1153
def test_evaluate_proxy_bypass_true(self):
1154
"""The host is not proxied"""
1155
self.assertEvaluateProxyBypass(True, 'example.com', 'example.com')
1156
self.assertEvaluateProxyBypass(True, 'bzr.example.com', '*example.com')
1158
def test_evaluate_proxy_bypass_false(self):
1159
"""The host is proxied"""
1160
self.assertEvaluateProxyBypass(False, 'bzr.example.com', None)
1162
def test_evaluate_proxy_bypass_unknown(self):
1163
"""The host is not explicitly proxied"""
1164
self.assertEvaluateProxyBypass(None, 'example.com', 'not.example.com')
1165
self.assertEvaluateProxyBypass(None, 'bzr.example.com', 'example.com')
1167
def test_evaluate_proxy_bypass_empty_entries(self):
1168
"""Ignore empty entries"""
1169
self.assertEvaluateProxyBypass(None, 'example.com', '')
1170
self.assertEvaluateProxyBypass(None, 'example.com', ',')
1171
self.assertEvaluateProxyBypass(None, 'example.com', 'foo,,bar')
1174
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
1175
"""Tests proxy server.
1177
Be aware that we do not setup a real proxy here. Instead, we
1178
check that the *connection* goes through the proxy by serving
1179
different content (the faked proxy server append '-proxied'
1183
scenarios = multiply_scenarios(
1184
vary_by_http_client_implementation(),
1185
vary_by_http_protocol_version(),
1188
# FIXME: We don't have an https server available, so we don't
1189
# test https connections. --vila toolongago
1192
super(TestProxyHttpServer, self).setUp()
1193
self.transport_secondary_server = http_utils.ProxyServer
1194
self.build_tree_contents([('foo', b'contents of foo\n'),
1195
('foo-proxied', b'proxied contents of foo\n')])
1196
# Let's setup some attributes for tests
1197
server = self.get_readonly_server()
1198
self.server_host_port = '%s:%d' % (server.host, server.port)
1199
self.no_proxy_host = self.server_host_port
1200
# The secondary server is the proxy
1201
self.proxy_url = self.get_secondary_url()
1203
def assertProxied(self):
1204
t = self.get_readonly_transport()
1205
self.assertEqual(b'proxied contents of foo\n', t.get('foo').read())
1207
def assertNotProxied(self):
1208
t = self.get_readonly_transport()
1209
self.assertEqual(b'contents of foo\n', t.get('foo').read())
1211
def test_http_proxy(self):
1212
self.overrideEnv('http_proxy', self.proxy_url)
1213
self.assertProxied()
1215
def test_HTTP_PROXY(self):
1216
self.overrideEnv('HTTP_PROXY', self.proxy_url)
1217
self.assertProxied()
1219
def test_all_proxy(self):
1220
self.overrideEnv('all_proxy', self.proxy_url)
1221
self.assertProxied()
1223
def test_ALL_PROXY(self):
1224
self.overrideEnv('ALL_PROXY', self.proxy_url)
1225
self.assertProxied()
1227
def test_http_proxy_with_no_proxy(self):
1228
self.overrideEnv('no_proxy', self.no_proxy_host)
1229
self.overrideEnv('http_proxy', self.proxy_url)
1230
self.assertNotProxied()
1232
def test_HTTP_PROXY_with_NO_PROXY(self):
1233
self.overrideEnv('NO_PROXY', self.no_proxy_host)
1234
self.overrideEnv('HTTP_PROXY', self.proxy_url)
1235
self.assertNotProxied()
1237
def test_all_proxy_with_no_proxy(self):
1238
self.overrideEnv('no_proxy', self.no_proxy_host)
1239
self.overrideEnv('all_proxy', self.proxy_url)
1240
self.assertNotProxied()
1242
def test_ALL_PROXY_with_NO_PROXY(self):
1243
self.overrideEnv('NO_PROXY', self.no_proxy_host)
1244
self.overrideEnv('ALL_PROXY', self.proxy_url)
1245
self.assertNotProxied()
1247
def test_http_proxy_without_scheme(self):
1248
self.overrideEnv('http_proxy', self.server_host_port)
1249
self.assertRaises(urlutils.InvalidURL, self.assertProxied)
1252
class TestRanges(http_utils.TestCaseWithWebserver):
1253
"""Test the Range header in GET methods."""
1255
scenarios = multiply_scenarios(
1256
vary_by_http_client_implementation(),
1257
vary_by_http_protocol_version(),
1261
super(TestRanges, self).setUp()
1262
self.build_tree_contents([('a', b'0123456789')],)
1264
def create_transport_readonly_server(self):
1265
return http_server.HttpServer(protocol_version=self._protocol_version)
1267
def _file_contents(self, relpath, ranges):
1268
t = self.get_readonly_transport()
1269
offsets = [(start, end - start + 1) for start, end in ranges]
1270
coalesce = t._coalesce_offsets
1271
coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
1272
code, data = t._get(relpath, coalesced)
1273
self.assertTrue(code in (200, 206), '_get returns: %d' % code)
1274
for start, end in ranges:
1276
yield data.read(end - start + 1)
1278
def _file_tail(self, relpath, tail_amount):
1279
t = self.get_readonly_transport()
1280
code, data = t._get(relpath, [], tail_amount)
1281
self.assertTrue(code in (200, 206), '_get returns: %d' % code)
1282
data.seek(-tail_amount, 2)
1283
return data.read(tail_amount)
1285
def test_range_header(self):
1288
[b'0', b'234'], list(self._file_contents('a', [(0, 0), (2, 4)])))
1290
def test_range_header_tail(self):
1291
self.assertEqual(b'789', self._file_tail('a', 3))
1293
def test_syntactically_invalid_range_header(self):
1294
self.assertListRaises(errors.InvalidHttpRange,
1295
self._file_contents, 'a', [(4, 3)])
1297
def test_semantically_invalid_range_header(self):
1298
self.assertListRaises(errors.InvalidHttpRange,
1299
self._file_contents, 'a', [(42, 128)])
1302
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1303
"""Test redirection between http servers."""
1305
scenarios = multiply_scenarios(
1306
vary_by_http_client_implementation(),
1307
vary_by_http_protocol_version(),
1311
super(TestHTTPRedirections, self).setUp()
1312
self.build_tree_contents([('a', b'0123456789'),
1314
b'# Bazaar revision bundle v0.9\n#\n')
1317
def test_redirected(self):
1318
self.assertRaises(errors.RedirectRequested,
1319
self.get_old_transport().get, 'a')
1322
self.get_new_transport().get('a').read())
1325
class RedirectedRequest(http.Request):
1326
"""Request following redirections. """
1328
init_orig = http.Request.__init__
1330
def __init__(self, method, url, *args, **kwargs):
1334
# Since the tests using this class will replace
1335
# http.Request, we can't just call the base class __init__
1337
RedirectedRequest.init_orig(self, method, url, *args, **kwargs)
1338
self.follow_redirections = True
1341
def install_redirected_request(test):
1342
test.overrideAttr(http, 'Request', RedirectedRequest)
1345
def cleanup_http_redirection_connections(test):
1346
# Some sockets are opened but never seen by _urllib, so we trap them at
1347
# the http level to be able to clean them up.
1348
def socket_disconnect(sock):
1350
sock.shutdown(socket.SHUT_RDWR)
1352
except socket.error:
1355
def connect(connection):
1356
test.http_connect_orig(connection)
1357
test.addCleanup(socket_disconnect, connection.sock)
1358
test.http_connect_orig = test.overrideAttr(
1359
http.HTTPConnection, 'connect', connect)
1361
def connect(connection):
1362
test.https_connect_orig(connection)
1363
test.addCleanup(socket_disconnect, connection.sock)
1364
test.https_connect_orig = test.overrideAttr(
1365
http.HTTPSConnection, 'connect', connect)
1368
class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):
1369
"""Test redirections.
1371
http implementations do not redirect silently anymore (they
1372
do not redirect at all in fact). The mechanism is still in
1373
place at the http.Request level and these tests
1377
scenarios = multiply_scenarios(
1378
vary_by_http_client_implementation(),
1379
vary_by_http_protocol_version(),
1383
super(TestHTTPSilentRedirections, self).setUp()
1384
install_redirected_request(self)
1385
cleanup_http_redirection_connections(self)
1386
self.build_tree_contents([('a', b'a'),
1388
('1/a', b'redirected once'),
1390
('2/a', b'redirected twice'),
1392
('3/a', b'redirected thrice'),
1394
('4/a', b'redirected 4 times'),
1396
('5/a', b'redirected 5 times'),
1399
def test_one_redirection(self):
1400
t = self.get_old_transport()
1401
new_prefix = 'http://%s:%s' % (self.new_server.host,
1402
self.new_server.port)
1403
self.old_server.redirections = \
1404
[('(.*)', r'%s/1\1' % (new_prefix), 301), ]
1407
t.request('GET', t._remote_path('a'), retries=1).read())
1409
def test_five_redirections(self):
1410
t = self.get_old_transport()
1411
old_prefix = 'http://%s:%s' % (self.old_server.host,
1412
self.old_server.port)
1413
new_prefix = 'http://%s:%s' % (self.new_server.host,
1414
self.new_server.port)
1415
self.old_server.redirections = [
1416
('/1(.*)', r'%s/2\1' % (old_prefix), 302),
1417
('/2(.*)', r'%s/3\1' % (old_prefix), 303),
1418
('/3(.*)', r'%s/4\1' % (old_prefix), 307),
1419
('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1420
('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1423
b'redirected 5 times',
1424
t.request('GET', t._remote_path('a'), retries=6).read())
1427
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
1428
"""Test transport.do_catching_redirections."""
1430
scenarios = multiply_scenarios(
1431
vary_by_http_client_implementation(),
1432
vary_by_http_protocol_version(),
1436
super(TestDoCatchRedirections, self).setUp()
1437
self.build_tree_contents([('a', b'0123456789'), ],)
1438
cleanup_http_redirection_connections(self)
1440
self.old_transport = self.get_old_transport()
1445
def test_no_redirection(self):
1446
t = self.get_new_transport()
1448
# We use None for redirected so that we fail if redirected
1449
self.assertEqual(b'0123456789',
1450
transport.do_catching_redirections(
1451
self.get_a, t, None).read())
1453
def test_one_redirection(self):
1454
self.redirections = 0
1456
def redirected(t, exception, redirection_notice):
1457
self.redirections += 1
1458
redirected_t = t._redirected_to(exception.source, exception.target)
1461
self.assertEqual(b'0123456789',
1462
transport.do_catching_redirections(
1463
self.get_a, self.old_transport, redirected).read())
1464
self.assertEqual(1, self.redirections)
1466
def test_redirection_loop(self):
1468
def redirected(transport, exception, redirection_notice):
1469
# By using the redirected url as a base dir for the
1470
# *old* transport, we create a loop: a => a/a =>
1472
return self.old_transport.clone(exception.target)
1474
self.assertRaises(errors.TooManyRedirections,
1475
transport.do_catching_redirections,
1476
self.get_a, self.old_transport, redirected)
1479
def _setup_authentication_config(**kwargs):
1480
conf = config.AuthenticationConfig()
1481
conf._get_config().update({'httptest': kwargs})
1485
class TestUrllib2AuthHandler(tests.TestCaseWithTransport):
1486
"""Unit tests for glue by which urllib2 asks us for authentication"""
1488
def test_get_user_password_without_port(self):
1489
"""We cope if urllib2 doesn't tell us the port.
1491
See https://bugs.launchpad.net/bzr/+bug/654684
1495
_setup_authentication_config(scheme='http', host='localhost',
1496
user=user, password=password)
1497
handler = http.HTTPAuthHandler()
1498
got_pass = handler.get_user_password(dict(
1505
self.assertEqual((user, password), got_pass)
1508
class TestAuth(http_utils.TestCaseWithWebserver):
1509
"""Test authentication scheme"""
1511
scenarios = multiply_scenarios(
1512
vary_by_http_client_implementation(),
1513
vary_by_http_protocol_version(),
1514
vary_by_http_auth_scheme(),
1518
super(TestAuth, self).setUp()
1519
self.server = self.get_readonly_server()
1520
self.build_tree_contents([('a', b'contents of a\n'),
1521
('b', b'contents of b\n'), ])
1523
def create_transport_readonly_server(self):
1524
server = self._auth_server(protocol_version=self._protocol_version)
1525
server._url_protocol = self._url_protocol
1528
def get_user_url(self, user, password):
1529
"""Build an url embedding user and password"""
1530
url = '%s://' % self.server._url_protocol
1531
if user is not None:
1533
if password is not None:
1534
url += ':' + password
1536
url += '%s:%s/' % (self.server.host, self.server.port)
1539
def get_user_transport(self, user, password):
1540
t = transport.get_transport_from_url(
1541
self.get_user_url(user, password))
1544
def test_no_user(self):
1545
self.server.add_user('joe', 'foo')
1546
t = self.get_user_transport(None, None)
1547
self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1548
# Only one 'Authentication Required' error should occur
1549
self.assertEqual(1, self.server.auth_required_errors)
1551
def test_empty_pass(self):
1552
self.server.add_user('joe', '')
1553
t = self.get_user_transport('joe', '')
1554
self.assertEqual(b'contents of a\n', t.get('a').read())
1555
# Only one 'Authentication Required' error should occur
1556
self.assertEqual(1, self.server.auth_required_errors)
1558
def test_user_pass(self):
1559
self.server.add_user('joe', 'foo')
1560
t = self.get_user_transport('joe', 'foo')
1561
self.assertEqual(b'contents of a\n', t.get('a').read())
1562
# Only one 'Authentication Required' error should occur
1563
self.assertEqual(1, self.server.auth_required_errors)
1565
def test_unknown_user(self):
1566
self.server.add_user('joe', 'foo')
1567
t = self.get_user_transport('bill', 'foo')
1568
self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1569
# Two 'Authentication Required' errors should occur (the
1570
# initial 'who are you' and 'I don't know you, who are
1572
self.assertEqual(2, self.server.auth_required_errors)
1574
def test_wrong_pass(self):
1575
self.server.add_user('joe', 'foo')
1576
t = self.get_user_transport('joe', 'bar')
1577
self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1578
# Two 'Authentication Required' errors should occur (the
1579
# initial 'who are you' and 'this is not you, who are you')
1580
self.assertEqual(2, self.server.auth_required_errors)
1582
def test_prompt_for_username(self):
1583
self.server.add_user('joe', 'foo')
1584
t = self.get_user_transport(None, None)
1585
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n')
1586
stdout, stderr = ui.ui_factory.stdout, ui.ui_factory.stderr
1587
self.assertEqual(b'contents of a\n', t.get('a').read())
1588
# stdin should be empty
1589
self.assertEqual('', ui.ui_factory.stdin.readline())
1591
expected_prompt = self._expected_username_prompt(t._unqualified_scheme)
1592
self.assertEqual(expected_prompt, stderr.read(len(expected_prompt)))
1593
self.assertEqual('', stdout.getvalue())
1594
self._check_password_prompt(t._unqualified_scheme, 'joe',
1597
def test_prompt_for_password(self):
1598
self.server.add_user('joe', 'foo')
1599
t = self.get_user_transport('joe', None)
1600
ui.ui_factory = tests.TestUIFactory(stdin='foo\n')
1601
stdout, stderr = ui.ui_factory.stdout, ui.ui_factory.stderr
1602
self.assertEqual(b'contents of a\n', t.get('a').read())
1603
# stdin should be empty
1604
self.assertEqual('', ui.ui_factory.stdin.readline())
1605
self._check_password_prompt(t._unqualified_scheme, 'joe',
1607
self.assertEqual('', stdout.getvalue())
1608
# And we shouldn't prompt again for a different request
1609
# against the same transport.
1610
self.assertEqual(b'contents of b\n', t.get('b').read())
1612
# And neither against a clone
1613
self.assertEqual(b'contents of b\n', t2.get('b').read())
1614
# Only one 'Authentication Required' error should occur
1615
self.assertEqual(1, self.server.auth_required_errors)
1617
def _check_password_prompt(self, scheme, user, actual_prompt):
1618
expected_prompt = (self._password_prompt_prefix
1619
+ ("%s %s@%s:%d, Realm: '%s' password: "
1621
user, self.server.host, self.server.port,
1622
self.server.auth_realm)))
1623
self.assertEqual(expected_prompt, actual_prompt)
1625
def _expected_username_prompt(self, scheme):
1626
return (self._username_prompt_prefix
1627
+ "%s %s:%d, Realm: '%s' username: " % (scheme.upper(),
1628
self.server.host, self.server.port,
1629
self.server.auth_realm))
1631
def test_no_prompt_for_password_when_using_auth_config(self):
1634
stdin_content = 'bar\n' # Not the right password
1635
self.server.add_user(user, password)
1636
t = self.get_user_transport(user, None)
1637
ui.ui_factory = tests.TestUIFactory(stdin=stdin_content)
1638
# Create a minimal config file with the right password
1639
_setup_authentication_config(scheme='http', port=self.server.port,
1640
user=user, password=password)
1641
# Issue a request to the server to connect
1642
with t.get('a') as f:
1643
self.assertEqual(b'contents of a\n', f.read())
1644
# stdin should have been left untouched
1645
self.assertEqual(stdin_content, ui.ui_factory.stdin.readline())
1646
# Only one 'Authentication Required' error should occur
1647
self.assertEqual(1, self.server.auth_required_errors)
1649
def test_changing_nonce(self):
1650
if self._auth_server not in (http_utils.HTTPDigestAuthServer,
1651
http_utils.ProxyDigestAuthServer):
1652
raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
1653
self.server.add_user('joe', 'foo')
1654
t = self.get_user_transport('joe', 'foo')
1655
with t.get('a') as f:
1656
self.assertEqual(b'contents of a\n', f.read())
1657
with t.get('b') as f:
1658
self.assertEqual(b'contents of b\n', f.read())
1659
# Only one 'Authentication Required' error should have
1661
self.assertEqual(1, self.server.auth_required_errors)
1662
# The server invalidates the current nonce
1663
self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1664
self.assertEqual(b'contents of a\n', t.get('a').read())
1665
# Two 'Authentication Required' errors should occur (the
1666
# initial 'who are you' and a second 'who are you' with the new nonce)
1667
self.assertEqual(2, self.server.auth_required_errors)
1669
def test_user_from_auth_conf(self):
1672
self.server.add_user(user, password)
1673
_setup_authentication_config(scheme='http', port=self.server.port,
1674
user=user, password=password)
1675
t = self.get_user_transport(None, None)
1676
# Issue a request to the server to connect
1677
with t.get('a') as f:
1678
self.assertEqual(b'contents of a\n', f.read())
1679
# Only one 'Authentication Required' error should occur
1680
self.assertEqual(1, self.server.auth_required_errors)
1682
def test_no_credential_leaks_in_log(self):
1683
self.overrideAttr(debug, 'debug_flags', {'http'})
1685
password = 'very-sensitive-password'
1686
self.server.add_user(user, password)
1687
t = self.get_user_transport(user, password)
1688
# Capture the debug calls to mutter
1692
lines = args[0] % args[1:]
1693
# Some calls output multiple lines, just split them now since we
1694
# care about a single one later.
1695
self.mutters.extend(lines.splitlines())
1696
self.overrideAttr(trace, 'mutter', mutter)
1697
# Issue a request to the server to connect
1698
self.assertEqual(True, t.has('a'))
1699
# Only one 'Authentication Required' error should occur
1700
self.assertEqual(1, self.server.auth_required_errors)
1701
# Since the authentification succeeded, there should be a corresponding
1703
sent_auth_headers = [line for line in self.mutters
1704
if line.startswith('> %s' % (self._auth_header,))]
1705
self.assertLength(1, sent_auth_headers)
1706
self.assertStartsWith(sent_auth_headers[0],
1707
'> %s: <masked>' % (self._auth_header,))
1710
class TestProxyAuth(TestAuth):
1711
"""Test proxy authentication schemes.
1713
This inherits from TestAuth to tweak the setUp and filter some failing
1717
scenarios = multiply_scenarios(
1718
vary_by_http_client_implementation(),
1719
vary_by_http_protocol_version(),
1720
vary_by_http_proxy_auth_scheme(),
1724
super(TestProxyAuth, self).setUp()
1725
# Override the contents to avoid false positives
1726
self.build_tree_contents([('a', b'not proxied contents of a\n'),
1727
('b', b'not proxied contents of b\n'),
1728
('a-proxied', b'contents of a\n'),
1729
('b-proxied', b'contents of b\n'),
1732
def get_user_transport(self, user, password):
1733
proxy_url = self.get_user_url(user, password)
1734
self.overrideEnv('all_proxy', proxy_url)
1735
return TestAuth.get_user_transport(self, user, password)
1738
class NonClosingBytesIO(io.BytesIO):
1741
"""Ignore and leave file open."""
1744
class SampleSocket(object):
1745
"""A socket-like object for use in testing the HTTP request handler."""
1747
def __init__(self, socket_read_content):
1748
"""Constructs a sample socket.
1750
:param socket_read_content: a byte sequence
1752
self.readfile = io.BytesIO(socket_read_content)
1753
self.writefile = NonClosingBytesIO()
1756
"""Ignore and leave files alone."""
1758
def sendall(self, bytes):
1759
self.writefile.write(bytes)
1761
def makefile(self, mode='r', bufsize=None):
1763
return self.readfile
1765
return self.writefile
1768
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1770
scenarios = multiply_scenarios(
1771
vary_by_http_client_implementation(),
1772
vary_by_http_protocol_version(),
1776
super(SmartHTTPTunnellingTest, self).setUp()
1777
# We use the VFS layer as part of HTTP tunnelling tests.
1778
self.overrideEnv('BRZ_NO_SMART_VFS', None)
1779
self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1780
self.http_server = self.get_readonly_server()
1782
def create_transport_readonly_server(self):
1783
server = http_utils.HTTPServerWithSmarts(
1784
protocol_version=self._protocol_version)
1785
server._url_protocol = self._url_protocol
1788
def test_open_controldir(self):
1789
branch = self.make_branch('relpath')
1790
url = self.http_server.get_url() + 'relpath'
1791
bd = controldir.ControlDir.open(url)
1792
self.addCleanup(bd.transport.disconnect)
1793
self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1795
def test_bulk_data(self):
1796
# We should be able to send and receive bulk data in a single message.
1797
# The 'readv' command in the smart protocol both sends and receives
1798
# bulk data, so we use that.
1799
self.build_tree(['data-file'])
1800
http_transport = transport.get_transport_from_url(
1801
self.http_server.get_url())
1802
medium = http_transport.get_smart_medium()
1803
# Since we provide the medium, the url below will be mostly ignored
1804
# during the test, as long as the path is '/'.
1805
remote_transport = remote.RemoteTransport('bzr://fake_host/',
1808
[(0, b"c")], list(remote_transport.readv("data-file", [(0, 1)])))
1810
def test_http_send_smart_request(self):
1812
post_body = b'hello\n'
1813
expected_reply_body = b'ok\x012\n'
1815
http_transport = transport.get_transport_from_url(
1816
self.http_server.get_url())
1817
medium = http_transport.get_smart_medium()
1818
response = medium.send_http_smart_request(post_body)
1819
reply_body = response.read()
1820
self.assertEqual(expected_reply_body, reply_body)
1822
def test_smart_http_server_post_request_handler(self):
1823
httpd = self.http_server.server
1825
socket = SampleSocket(
1826
b'POST /.bzr/smart %s \r\n' % self._protocol_version.encode('ascii') +
1827
# HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
1829
b'Content-Length: 6\r\n'
1832
# Beware: the ('localhost', 80) below is the
1833
# client_address parameter, but we don't have one because
1834
# we have defined a socket which is not bound to an
1835
# address. The test framework never uses this client
1836
# address, so far...
1837
request_handler = http_utils.SmartRequestHandler(socket,
1840
response = socket.writefile.getvalue()
1841
self.assertStartsWith(
1843
b'%s 200 ' % self._protocol_version.encode('ascii'))
1844
# This includes the end of the HTTP headers, and all the body.
1845
expected_end_of_response = b'\r\n\r\nok\x012\n'
1846
self.assertEndsWith(response, expected_end_of_response)
1849
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
1850
"""No smart server here request handler."""
1853
self.send_error(403, "Forbidden")
1856
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
1857
"""Test smart client behaviour against an http server without smarts."""
1859
_req_handler_class = ForbiddenRequestHandler
1861
def test_probe_smart_server(self):
1862
"""Test error handling against server refusing smart requests."""
1863
t = self.get_readonly_transport()
1864
# No need to build a valid smart request here, the server will not even
1865
# try to interpret it.
1866
self.assertRaises(errors.SmartProtocolError,
1867
t.get_smart_medium().send_http_smart_request,
1871
class Test_redirected_to(tests.TestCase):
1873
scenarios = vary_by_http_client_implementation()
1875
def test_redirected_to_subdir(self):
1876
t = self._transport('http://www.example.com/foo')
1877
r = t._redirected_to('http://www.example.com/foo',
1878
'http://www.example.com/foo/subdir')
1879
self.assertIsInstance(r, type(t))
1880
# Both transports share the some connection
1881
self.assertEqual(t._get_connection(), r._get_connection())
1882
self.assertEqual('http://www.example.com/foo/subdir/', r.base)
1884
def test_redirected_to_self_with_slash(self):
1885
t = self._transport('http://www.example.com/foo')
1886
r = t._redirected_to('http://www.example.com/foo',
1887
'http://www.example.com/foo/')
1888
self.assertIsInstance(r, type(t))
1889
# Both transports share the some connection (one can argue that we
1890
# should return the exact same transport here, but that seems
1892
self.assertEqual(t._get_connection(), r._get_connection())
1894
def test_redirected_to_host(self):
1895
t = self._transport('http://www.example.com/foo')
1896
r = t._redirected_to('http://www.example.com/foo',
1897
'http://foo.example.com/foo/subdir')
1898
self.assertIsInstance(r, type(t))
1899
self.assertEqual('http://foo.example.com/foo/subdir/',
1902
def test_redirected_to_same_host_sibling_protocol(self):
1903
t = self._transport('http://www.example.com/foo')
1904
r = t._redirected_to('http://www.example.com/foo',
1905
'https://www.example.com/foo')
1906
self.assertIsInstance(r, type(t))
1907
self.assertEqual('https://www.example.com/foo/',
1910
def test_redirected_to_same_host_different_protocol(self):
1911
t = self._transport('http://www.example.com/foo')
1912
r = t._redirected_to('http://www.example.com/foo',
1913
'bzr://www.example.com/foo')
1914
self.assertNotEqual(type(r), type(t))
1915
self.assertEqual('bzr://www.example.com/foo/', r.external_url())
1917
def test_redirected_to_same_host_specific_implementation(self):
1918
t = self._transport('http://www.example.com/foo')
1919
r = t._redirected_to('http://www.example.com/foo',
1920
'https+urllib://www.example.com/foo')
1921
self.assertEqual('https://www.example.com/foo/', r.external_url())
1923
def test_redirected_to_different_host_same_user(self):
1924
t = self._transport('http://joe@www.example.com/foo')
1925
r = t._redirected_to('http://www.example.com/foo',
1926
'https://foo.example.com/foo')
1927
self.assertIsInstance(r, type(t))
1928
self.assertEqual(t._parsed_url.user, r._parsed_url.user)
1929
self.assertEqual('https://joe@foo.example.com/foo/', r.external_url())
1932
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1933
"""Request handler for a unique and pre-defined request.
1935
The only thing we care about here is how many bytes travel on the wire. But
1936
since we want to measure it for a real http client, we have to send it
1939
We expect to receive a *single* request nothing more (and we won't even
1940
check what request it is, we just measure the bytes read until an empty
1944
def _handle_one_request(self):
1945
tcs = self.server.test_case_server
1946
requestline = self.rfile.readline()
1947
headers = parse_headers(self.rfile)
1948
bytes_read = len(headers.as_bytes())
1949
bytes_read += headers.as_bytes().count(b'\n')
1950
bytes_read += len(requestline)
1951
if requestline.startswith(b'POST'):
1952
# The body should be a single line (or we don't know where it ends
1953
# and we don't want to issue a blocking read)
1954
body = self.rfile.readline()
1955
bytes_read += len(body)
1956
tcs.bytes_read = bytes_read
1958
# We set the bytes written *before* issuing the write, the client is
1959
# supposed to consume every produced byte *before* checking that value.
1961
# Doing the oppposite may lead to test failure: we may be interrupted
1962
# after the write but before updating the value. The client can then
1963
# continue and read the value *before* we can update it. And yes,
1964
# this has been observed -- vila 20090129
1965
tcs.bytes_written = len(tcs.canned_response)
1966
self.wfile.write(tcs.canned_response)
1969
class ActivityServerMixin(object):
1971
def __init__(self, protocol_version):
1972
super(ActivityServerMixin, self).__init__(
1973
request_handler=PredefinedRequestHandler,
1974
protocol_version=protocol_version)
1975
# Bytes read and written by the server
1977
self.bytes_written = 0
1978
self.canned_response = None
1981
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
1985
if features.HTTPSServerFeature.available():
1986
from . import https_server
1988
class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
1992
class TestActivityMixin(object):
1993
"""Test socket activity reporting.
1995
We use a special purpose server to control the bytes sent and received and
1996
be able to predict the activity on the client socket.
2000
self.server = self._activity_server(self._protocol_version)
2001
self.server.start_server()
2002
self.addCleanup(self.server.stop_server)
2003
_activities = {} # Don't close over self and create a cycle
2005
def report_activity(t, bytes, direction):
2006
count = _activities.get(direction, 0)
2008
_activities[direction] = count
2009
self.activities = _activities
2010
# We override at class level because constructors may propagate the
2011
# bound method and render instance overriding ineffective (an
2012
# alternative would be to define a specific ui factory instead...)
2013
self.overrideAttr(self._transport, '_report_activity', report_activity)
2015
def get_transport(self):
2016
t = self._transport(self.server.get_url())
2017
# FIXME: Needs cleanup -- vila 20100611
2020
def assertActivitiesMatch(self):
2021
self.assertEqual(self.server.bytes_read,
2022
self.activities.get('write', 0), 'written bytes')
2023
self.assertEqual(self.server.bytes_written,
2024
self.activities.get('read', 0), 'read bytes')
2027
self.server.canned_response = b'''HTTP/1.1 200 OK\r
2028
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2029
Server: Apache/2.0.54 (Fedora)\r
2030
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2031
ETag: "56691-23-38e9ae00"\r
2032
Accept-Ranges: bytes\r
2033
Content-Length: 35\r
2035
Content-Type: text/plain; charset=UTF-8\r
2037
Bazaar-NG meta directory, format 1
2039
t = self.get_transport()
2040
self.assertEqual(b'Bazaar-NG meta directory, format 1\n',
2041
t.get('foo/bar').read())
2042
self.assertActivitiesMatch()
2045
self.server.canned_response = b'''HTTP/1.1 200 OK\r
2046
Server: SimpleHTTP/0.6 Python/2.5.2\r
2047
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
2048
Content-type: application/octet-stream\r
2049
Content-Length: 20\r
2050
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
2053
t = self.get_transport()
2054
self.assertTrue(t.has('foo/bar'))
2055
self.assertActivitiesMatch()
2057
def test_readv(self):
2058
self.server.canned_response = b'''HTTP/1.1 206 Partial Content\r
2059
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
2060
Server: Apache/2.0.54 (Fedora)\r
2061
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
2062
ETag: "238a3c-16ec2-805c5540"\r
2063
Accept-Ranges: bytes\r
2064
Content-Length: 1534\r
2066
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
2069
--418470f848b63279b\r
2070
Content-type: text/plain; charset=UTF-8\r
2071
Content-range: bytes 0-254/93890\r
2073
mbp@sourcefrog.net-20050309040815-13242001617e4a06
2074
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
2075
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
2076
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
2077
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
2079
--418470f848b63279b\r
2080
Content-type: text/plain; charset=UTF-8\r
2081
Content-range: bytes 1000-2049/93890\r
2084
mbp@sourcefrog.net-20050311063625-07858525021f270b
2085
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
2086
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
2087
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
2088
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
2089
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
2090
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
2091
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
2092
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
2093
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
2094
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
2095
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
2096
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
2097
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
2098
mbp@sourcefrog.net-20050313120651-497bd231b19df600
2099
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
2100
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
2101
mbp@sourcefrog.net-20050314025539-637a636692c055cf
2102
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
2103
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
2105
--418470f848b63279b--\r
2107
t = self.get_transport()
2108
# Remember that the request is ignored and that the ranges below
2109
# doesn't have to match the canned response.
2110
l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
2111
# Force consumption of the last bytesrange boundary
2112
t._get_connection().cleanup_pipe()
2113
self.assertEqual(2, len(l))
2114
self.assertActivitiesMatch()
2116
def test_post(self):
2117
self.server.canned_response = b'''HTTP/1.1 200 OK\r
2118
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
2119
Server: Apache/2.0.54 (Fedora)\r
2120
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
2121
ETag: "56691-23-38e9ae00"\r
2122
Accept-Ranges: bytes\r
2123
Content-Length: 35\r
2125
Content-Type: text/plain; charset=UTF-8\r
2127
lalala whatever as long as itsssss
2129
t = self.get_transport()
2130
# We must send a single line of body bytes, see
2131
# PredefinedRequestHandler._handle_one_request
2132
code, f = t._post(b'abc def end-of-body\n')
2133
self.assertEqual(b'lalala whatever as long as itsssss\n', f.read())
2134
self.assertActivitiesMatch()
2137
class TestActivity(tests.TestCase, TestActivityMixin):
2139
scenarios = multiply_scenarios(
2140
vary_by_http_activity(),
2141
vary_by_http_protocol_version(),
2145
super(TestActivity, self).setUp()
2146
TestActivityMixin.setUp(self)
2149
class TestNoReportActivity(tests.TestCase, TestActivityMixin):
2151
# Unlike TestActivity, we are really testing ReportingFileSocket and
2152
# ReportingSocket, so we don't need all the parametrization. Since
2153
# ReportingFileSocket and ReportingSocket are wrappers, it's easier to
2154
# test them through their use by the transport than directly (that's a
2155
# bit less clean but far more simpler and effective).
2156
_activity_server = ActivityHTTPServer
2157
_protocol_version = 'HTTP/1.1'
2160
super(TestNoReportActivity, self).setUp()
2161
self._transport = HttpTransport
2162
TestActivityMixin.setUp(self)
2164
def assertActivitiesMatch(self):
2165
# Nothing to check here
2169
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
2170
"""Test authentication on the redirected http server."""
2172
scenarios = vary_by_http_protocol_version()
2174
_auth_header = 'Authorization'
2175
_password_prompt_prefix = ''
2176
_username_prompt_prefix = ''
2177
_auth_server = http_utils.HTTPBasicAuthServer
2178
_transport = HttpTransport
2181
super(TestAuthOnRedirected, self).setUp()
2182
self.build_tree_contents([('a', b'a'),
2184
('1/a', b'redirected once'),
2186
new_prefix = 'http://%s:%s' % (self.new_server.host,
2187
self.new_server.port)
2188
self.old_server.redirections = [
2189
('(.*)', r'%s/1\1' % (new_prefix), 301), ]
2190
self.old_transport = self.get_old_transport()
2191
self.new_server.add_user('joe', 'foo')
2192
cleanup_http_redirection_connections(self)
2194
def create_transport_readonly_server(self):
2195
server = self._auth_server(protocol_version=self._protocol_version)
2196
server._url_protocol = self._url_protocol
2202
def test_auth_on_redirected_via_do_catching_redirections(self):
2203
self.redirections = 0
2205
def redirected(t, exception, redirection_notice):
2206
self.redirections += 1
2207
redirected_t = t._redirected_to(exception.source, exception.target)
2208
self.addCleanup(redirected_t.disconnect)
2211
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n')
2212
self.assertEqual(b'redirected once',
2213
transport.do_catching_redirections(
2214
self.get_a, self.old_transport, redirected).read())
2215
self.assertEqual(1, self.redirections)
2216
# stdin should be empty
2217
self.assertEqual('', ui.ui_factory.stdin.readline())
2218
# stdout should be empty, stderr will contains the prompts
2219
self.assertEqual('', ui.ui_factory.stdout.getvalue())
2221
def test_auth_on_redirected_via_following_redirections(self):
2222
self.new_server.add_user('joe', 'foo')
2223
ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n')
2224
t = self.old_transport
2225
new_prefix = 'http://%s:%s' % (self.new_server.host,
2226
self.new_server.port)
2227
self.old_server.redirections = [
2228
('(.*)', r'%s/1\1' % (new_prefix), 301), ]
2231
t.request('GET', t.abspath('a'), retries=3).read())
2232
# stdin should be empty
2233
self.assertEqual('', ui.ui_factory.stdin.readline())
2234
# stdout should be empty, stderr will contains the prompts
2235
self.assertEqual('', ui.ui_factory.stdout.getvalue())