~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-11-04 18:51:39 UTC
  • mfrom: (2961.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20071104185139-kaio3sneodg2kp71
Authentication ring implementation (read-only)

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
"""Tests for HTTP implementations.
18
 
 
19
 
This module defines a load_tests() method that parametrize tests classes for
20
 
transport implementation, http protocol versions and authentication schemes.
21
 
"""
 
17
# FIXME: This test should be repeated for each available http client
 
18
# implementation; at the moment we have urllib and pycurl.
22
19
 
23
20
# TODO: Should be renamed to bzrlib.transport.http.tests?
24
21
# TODO: What about renaming to bzrlib.tests.transport.http ?
25
22
 
26
23
from cStringIO import StringIO
27
 
import httplib
28
24
import os
29
25
import select
30
 
import SimpleHTTPServer
31
26
import socket
32
27
import sys
33
28
import threading
34
29
 
35
30
import bzrlib
36
31
from bzrlib import (
37
 
    bzrdir,
38
32
    config,
39
33
    errors,
40
34
    osutils,
41
 
    remote as _mod_remote,
42
 
    tests,
43
 
    transport,
44
35
    ui,
45
36
    urlutils,
46
37
    )
47
38
from bzrlib.tests import (
48
 
    http_server,
49
 
    http_utils,
 
39
    TestCase,
 
40
    TestUIFactory,
 
41
    TestSkipped,
 
42
    StringIOWrapper,
 
43
    )
 
44
from bzrlib.tests.HttpServer import (
 
45
    HttpServer,
 
46
    HttpServer_PyCurl,
 
47
    HttpServer_urllib,
 
48
    )
 
49
from bzrlib.tests.HTTPTestUtil import (
 
50
    BadProtocolRequestHandler,
 
51
    BadStatusRequestHandler,
 
52
    ForbiddenRequestHandler,
 
53
    HTTPBasicAuthServer,
 
54
    HTTPDigestAuthServer,
 
55
    HTTPServerRedirecting,
 
56
    InvalidStatusRequestHandler,
 
57
    LimitedRangeHTTPServer,
 
58
    NoRangeRequestHandler,
 
59
    ProxyBasicAuthServer,
 
60
    ProxyDigestAuthServer,
 
61
    ProxyServer,
 
62
    SingleRangeRequestHandler,
 
63
    SingleOnlyRangeRequestHandler,
 
64
    TestCaseWithRedirectedWebserver,
 
65
    TestCaseWithTwoWebservers,
 
66
    TestCaseWithWebserver,
 
67
    WallRequestHandler,
50
68
    )
51
69
from bzrlib.transport import (
52
 
    http,
53
 
    remote,
 
70
    _CoalescedOffset,
 
71
    do_catching_redirections,
 
72
    get_transport,
 
73
    Transport,
54
74
    )
55
75
from bzrlib.transport.http import (
56
 
    _urllib,
 
76
    extract_auth,
 
77
    HttpTransportBase,
57
78
    _urllib2_wrappers,
58
79
    )
59
 
 
60
 
 
61
 
try:
62
 
    from bzrlib.transport.http._pycurl import PyCurlTransport
63
 
    pycurl_present = True
64
 
except errors.DependencyNotPresent:
65
 
    pycurl_present = False
66
 
 
67
 
 
68
 
class TransportAdapter(tests.TestScenarioApplier):
69
 
    """Generate the same test for each transport implementation."""
70
 
 
71
 
    def __init__(self):
72
 
        transport_scenarios = [
73
 
            ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
74
 
                            _server=http_server.HttpServer_urllib,
75
 
                            _qualified_prefix='http+urllib',)),
76
 
            ]
77
 
        if pycurl_present:
78
 
            transport_scenarios.append(
79
 
                ('pycurl', dict(_transport=PyCurlTransport,
80
 
                                _server=http_server.HttpServer_PyCurl,
81
 
                                _qualified_prefix='http+pycurl',)))
82
 
        self.scenarios = transport_scenarios
83
 
 
84
 
 
85
 
class TransportProtocolAdapter(TransportAdapter):
86
 
    """Generate the same test for each protocol implementation.
87
 
 
88
 
    In addition to the transport adaptatation that we inherit from.
89
 
    """
90
 
 
91
 
    def __init__(self):
92
 
        super(TransportProtocolAdapter, self).__init__()
93
 
        protocol_scenarios = [
94
 
            ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
95
 
            ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
96
 
            ]
97
 
        self.scenarios = tests.multiply_scenarios(self.scenarios,
98
 
                                                  protocol_scenarios)
99
 
 
100
 
 
101
 
class TransportProtocolAuthenticationAdapter(TransportProtocolAdapter):
102
 
    """Generate the same test for each authentication scheme implementation.
103
 
 
104
 
    In addition to the protocol adaptatation that we inherit from.
105
 
    """
106
 
 
107
 
    def __init__(self):
108
 
        super(TransportProtocolAuthenticationAdapter, self).__init__()
109
 
        auth_scheme_scenarios = [
110
 
            ('basic', dict(_auth_scheme='basic')),
111
 
            ('digest', dict(_auth_scheme='digest')),
112
 
            ]
113
 
 
114
 
        self.scenarios = tests.multiply_scenarios(self.scenarios,
115
 
                                                  auth_scheme_scenarios)
116
 
 
117
 
def load_tests(standard_tests, module, loader):
118
 
    """Multiply tests for http clients and protocol versions."""
119
 
    # one for each transport
120
 
    t_adapter = TransportAdapter()
121
 
    t_classes= (TestHttpTransportRegistration,
122
 
                TestHttpTransportUrls,
123
 
                )
124
 
    is_testing_for_transports = tests.condition_isinstance(t_classes)
125
 
 
126
 
    # multiplied by one for each protocol version
127
 
    tp_adapter = TransportProtocolAdapter()
128
 
    tp_classes= (SmartHTTPTunnellingTest,
129
 
                 TestDoCatchRedirections,
130
 
                 TestHTTPConnections,
131
 
                 TestHTTPRedirections,
132
 
                 TestHTTPSilentRedirections,
133
 
                 TestLimitedRangeRequestServer,
134
 
                 TestPost,
135
 
                 TestProxyHttpServer,
136
 
                 TestRanges,
137
 
                 TestSpecificRequestHandler,
138
 
                 )
139
 
    is_also_testing_for_protocols = tests.condition_isinstance(tp_classes)
140
 
 
141
 
    # multiplied by one for each authentication scheme
142
 
    tpa_adapter = TransportProtocolAuthenticationAdapter()
143
 
    tpa_classes = (TestAuth,
144
 
                   )
145
 
    is_also_testing_for_authentication = tests.condition_isinstance(
146
 
        tpa_classes)
147
 
 
148
 
    result = loader.suiteClass()
149
 
    for test_class in tests.iter_suite_tests(standard_tests):
150
 
        # Each test class is either standalone or testing for some combination
151
 
        # of transport, protocol version, authentication scheme. Use the right
152
 
        # adpater (or none) depending on the class.
153
 
        if is_testing_for_transports(test_class):
154
 
            result.addTests(t_adapter.adapt(test_class))
155
 
        elif is_also_testing_for_protocols(test_class):
156
 
            result.addTests(tp_adapter.adapt(test_class))
157
 
        elif is_also_testing_for_authentication(test_class):
158
 
            result.addTests(tpa_adapter.adapt(test_class))
159
 
        else:
160
 
            result.addTest(test_class)
161
 
    return result
 
80
from bzrlib.transport.http._urllib import HttpTransport_urllib
 
81
from bzrlib.transport.http._urllib2_wrappers import (
 
82
    ProxyHandler,
 
83
    Request,
 
84
    )
162
85
 
163
86
 
164
87
class FakeManager(object):
227
150
        self.port = None
228
151
 
229
152
 
230
 
class TestHTTPServer(tests.TestCase):
231
 
    """Test the HTTP servers implementations."""
232
 
 
233
 
    def test_invalid_protocol(self):
234
 
        class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
235
 
 
236
 
            protocol_version = 'HTTP/0.1'
237
 
 
238
 
        server = http_server.HttpServer(BogusRequestHandler)
239
 
        try:
240
 
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
241
 
        except:
242
 
            server.tearDown()
243
 
            self.fail('HTTP Server creation did not raise UnknownProtocol')
244
 
 
245
 
    def test_force_invalid_protocol(self):
246
 
        server = http_server.HttpServer(protocol_version='HTTP/0.1')
247
 
        try:
248
 
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
249
 
        except:
250
 
            server.tearDown()
251
 
            self.fail('HTTP Server creation did not raise UnknownProtocol')
252
 
 
253
 
    def test_server_start_and_stop(self):
254
 
        server = http_server.HttpServer()
255
 
        server.setUp()
256
 
        self.assertTrue(server._http_running)
257
 
        server.tearDown()
258
 
        self.assertFalse(server._http_running)
259
 
 
260
 
    def test_create_http_server_one_zero(self):
261
 
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
262
 
 
263
 
            protocol_version = 'HTTP/1.0'
264
 
 
265
 
        server = http_server.HttpServer(RequestHandlerOneZero)
266
 
        server.setUp()
267
 
        self.addCleanup(server.tearDown)
268
 
        self.assertIsInstance(server._httpd, http_server.TestingHTTPServer)
269
 
 
270
 
    def test_create_http_server_one_one(self):
271
 
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
272
 
 
273
 
            protocol_version = 'HTTP/1.1'
274
 
 
275
 
        server = http_server.HttpServer(RequestHandlerOneOne)
276
 
        server.setUp()
277
 
        self.addCleanup(server.tearDown)
278
 
        self.assertIsInstance(server._httpd,
279
 
                              http_server.TestingThreadingHTTPServer)
280
 
 
281
 
    def test_create_http_server_force_one_one(self):
282
 
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
283
 
 
284
 
            protocol_version = 'HTTP/1.0'
285
 
 
286
 
        server = http_server.HttpServer(RequestHandlerOneZero,
287
 
                                        protocol_version='HTTP/1.1')
288
 
        server.setUp()
289
 
        self.addCleanup(server.tearDown)
290
 
        self.assertIsInstance(server._httpd,
291
 
                              http_server.TestingThreadingHTTPServer)
292
 
 
293
 
    def test_create_http_server_force_one_zero(self):
294
 
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
295
 
 
296
 
            protocol_version = 'HTTP/1.1'
297
 
 
298
 
        server = http_server.HttpServer(RequestHandlerOneOne,
299
 
                                        protocol_version='HTTP/1.0')
300
 
        server.setUp()
301
 
        self.addCleanup(server.tearDown)
302
 
        self.assertIsInstance(server._httpd,
303
 
                              http_server.TestingHTTPServer)
304
 
 
305
 
 
306
153
class TestWithTransport_pycurl(object):
307
154
    """Test case to inherit from if pycurl is present"""
308
155
 
311
158
            from bzrlib.transport.http._pycurl import PyCurlTransport
312
159
            return PyCurlTransport
313
160
        except errors.DependencyNotPresent:
314
 
            raise tests.TestSkipped('pycurl not present')
 
161
            raise TestSkipped('pycurl not present')
315
162
 
316
163
    _transport = property(_get_pycurl_maybe)
317
164
 
318
165
 
319
 
class TestHttpUrls(tests.TestCase):
 
166
class TestHttpUrls(TestCase):
320
167
 
321
168
    # TODO: This should be moved to authorization tests once they
322
169
    # are written.
323
170
 
324
171
    def test_url_parsing(self):
325
172
        f = FakeManager()
326
 
        url = http.extract_auth('http://example.com', f)
 
173
        url = extract_auth('http://example.com', f)
327
174
        self.assertEquals('http://example.com', url)
328
175
        self.assertEquals(0, len(f.credentials))
329
 
        url = http.extract_auth(
330
 
            'http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
 
176
        url = extract_auth('http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
331
177
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
332
178
        self.assertEquals(1, len(f.credentials))
333
179
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
334
180
                          f.credentials[0])
335
181
 
336
182
 
337
 
class TestHttpTransportUrls(tests.TestCase):
338
 
    """Test the http urls."""
 
183
class TestHttpTransportUrls(object):
 
184
    """Test the http urls.
 
185
 
 
186
    This MUST be used by daughter classes that also inherit from
 
187
    TestCase.
 
188
 
 
189
    We can't inherit directly from TestCase or the
 
190
    test framework will try to create an instance which cannot
 
191
    run, its implementation being incomplete.
 
192
    """
339
193
 
340
194
    def test_abs_url(self):
341
195
        """Construction of absolute http URLs"""
372
226
            server.tearDown()
373
227
 
374
228
 
375
 
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
 
229
class TestHttpUrls_urllib(TestHttpTransportUrls, TestCase):
 
230
    """Test http urls with urllib"""
 
231
 
 
232
    _transport = HttpTransport_urllib
 
233
    _server = HttpServer_urllib
 
234
    _qualified_prefix = 'http+urllib'
 
235
 
 
236
 
 
237
class TestHttpUrls_pycurl(TestWithTransport_pycurl, TestHttpTransportUrls,
 
238
                          TestCase):
 
239
    """Test http urls with pycurl"""
 
240
 
 
241
    _server = HttpServer_PyCurl
 
242
    _qualified_prefix = 'http+pycurl'
376
243
 
377
244
    # TODO: This should really be moved into another pycurl
378
245
    # specific test. When https tests will be implemented, take
387
254
        try:
388
255
            import pycurl
389
256
        except ImportError:
390
 
            raise tests.TestSkipped('pycurl not present')
391
 
 
392
 
        version_info_orig = pycurl.version_info
393
 
        try:
394
 
            # Now that we have pycurl imported, we can fake its version_info
395
 
            # This was taken from a windows pycurl without SSL
396
 
            # (thanks to bialix)
397
 
            pycurl.version_info = lambda : (2,
398
 
                                            '7.13.2',
399
 
                                            462082,
400
 
                                            'i386-pc-win32',
401
 
                                            2576,
402
 
                                            None,
403
 
                                            0,
404
 
                                            None,
405
 
                                            ('ftp', 'gopher', 'telnet',
406
 
                                             'dict', 'ldap', 'http', 'file'),
407
 
                                            None,
408
 
                                            0,
409
 
                                            None)
410
 
            self.assertRaises(errors.DependencyNotPresent, self._transport,
411
 
                              'https://launchpad.net')
412
 
        finally:
413
 
            # Restore the right function
414
 
            pycurl.version_info = version_info_orig
415
 
 
416
 
 
417
 
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
418
 
    """Test the http connections."""
 
257
            raise TestSkipped('pycurl not present')
 
258
        # Now that we have pycurl imported, we can fake its version_info
 
259
        # This was taken from a windows pycurl without SSL
 
260
        # (thanks to bialix)
 
261
        pycurl.version_info = lambda : (2,
 
262
                                        '7.13.2',
 
263
                                        462082,
 
264
                                        'i386-pc-win32',
 
265
                                        2576,
 
266
                                        None,
 
267
                                        0,
 
268
                                        None,
 
269
                                        ('ftp', 'gopher', 'telnet',
 
270
                                         'dict', 'ldap', 'http', 'file'),
 
271
                                        None,
 
272
                                        0,
 
273
                                        None)
 
274
        self.assertRaises(errors.DependencyNotPresent, self._transport,
 
275
                          'https://launchpad.net')
 
276
 
 
277
class TestHttpConnections(object):
 
278
    """Test the http connections.
 
279
 
 
280
    This MUST be used by daughter classes that also inherit from
 
281
    TestCaseWithWebserver.
 
282
 
 
283
    We can't inherit directly from TestCaseWithWebserver or the
 
284
    test framework will try to create an instance which cannot
 
285
    run, its implementation being incomplete.
 
286
    """
419
287
 
420
288
    def setUp(self):
421
 
        http_utils.TestCaseWithWebserver.setUp(self)
422
 
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
 
289
        TestCaseWithWebserver.setUp(self)
 
290
        self.build_tree(['xxx', 'foo/', 'foo/bar'], line_endings='binary',
423
291
                        transport=self.get_transport())
424
292
 
425
293
    def test_http_has(self):
471
339
            socket.setdefaulttimeout(default_timeout)
472
340
 
473
341
 
474
 
class TestHttpTransportRegistration(tests.TestCase):
 
342
class TestHttpConnections_urllib(TestHttpConnections, TestCaseWithWebserver):
 
343
    """Test http connections with urllib"""
 
344
 
 
345
    _transport = HttpTransport_urllib
 
346
 
 
347
 
 
348
 
 
349
class TestHttpConnections_pycurl(TestWithTransport_pycurl,
 
350
                                 TestHttpConnections,
 
351
                                 TestCaseWithWebserver):
 
352
    """Test http connections with pycurl"""
 
353
 
 
354
 
 
355
class TestHttpTransportRegistration(TestCase):
475
356
    """Test registrations of various http implementations"""
476
357
 
477
358
    def test_http_registered(self):
478
 
        t = transport.get_transport('%s://foo.com/' % self._qualified_prefix)
479
 
        self.assertIsInstance(t, transport.Transport)
480
 
        self.assertIsInstance(t, self._transport)
481
 
 
482
 
 
483
 
class TestPost(tests.TestCase):
484
 
 
485
 
    def test_post_body_is_received(self):
 
359
        # urlllib should always be present
 
360
        t = get_transport('http+urllib://bzr.google.com/')
 
361
        self.assertIsInstance(t, Transport)
 
362
        self.assertIsInstance(t, HttpTransport_urllib)
 
363
 
 
364
 
 
365
class TestPost(object):
 
366
 
 
367
    def _test_post_body_is_received(self, scheme):
486
368
        server = RecordingServer(expect_body_tail='end-of-body')
487
369
        server.setUp()
488
370
        self.addCleanup(server.tearDown)
489
 
        scheme = self._qualified_prefix
490
371
        url = '%s://%s:%s/' % (scheme, server.host, server.port)
491
 
        http_transport = self._transport(url)
 
372
        try:
 
373
            http_transport = get_transport(url)
 
374
        except errors.UnsupportedProtocol:
 
375
            raise TestSkipped('%s not available' % scheme)
492
376
        code, response = http_transport._post('abc def end-of-body')
493
377
        self.assertTrue(
494
378
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
500
384
            server.received_bytes.endswith('\r\n\r\nabc def end-of-body'))
501
385
 
502
386
 
503
 
class TestRangeHeader(tests.TestCase):
 
387
class TestPost_urllib(TestCase, TestPost):
 
388
    """TestPost for urllib implementation"""
 
389
 
 
390
    _transport = HttpTransport_urllib
 
391
 
 
392
    def test_post_body_is_received_urllib(self):
 
393
        self._test_post_body_is_received('http+urllib')
 
394
 
 
395
 
 
396
class TestPost_pycurl(TestWithTransport_pycurl, TestCase, TestPost):
 
397
    """TestPost for pycurl implementation"""
 
398
 
 
399
    def test_post_body_is_received_pycurl(self):
 
400
        self._test_post_body_is_received('http+pycurl')
 
401
 
 
402
 
 
403
class TestRangeHeader(TestCase):
504
404
    """Test range_header method"""
505
405
 
506
406
    def check_header(self, value, ranges=[], tail=0):
507
407
        offsets = [ (start, end - start + 1) for start, end in ranges]
508
 
        coalesce = transport.Transport._coalesce_offsets
 
408
        coalesce = Transport._coalesce_offsets
509
409
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
510
 
        range_header = http.HttpTransportBase._range_header
 
410
        range_header = HttpTransportBase._range_header
511
411
        self.assertEqual(value, range_header(coalesced, tail))
512
412
 
513
413
    def test_range_header_single(self):
528
428
                          tail=50)
529
429
 
530
430
 
531
 
class TestSpecificRequestHandler(http_utils.TestCaseWithWebserver):
532
 
    """Tests a specific request handler.
533
 
 
534
 
    Daughter classes are expected to override _req_handler_class
535
 
    """
536
 
 
537
 
    # Provide a useful default
538
 
    _req_handler_class = http_server.TestingHTTPRequestHandler
539
 
 
540
 
    def create_transport_readonly_server(self):
541
 
        return http_server.HttpServer(self._req_handler_class,
542
 
                                      protocol_version=self._protocol_version)
543
 
 
544
 
    def _testing_pycurl(self):
545
 
        return pycurl_present and self._transport == PyCurlTransport
546
 
 
547
 
 
548
 
class WallRequestHandler(http_server.TestingHTTPRequestHandler):
549
 
    """Whatever request comes in, close the connection"""
550
 
 
551
 
    def handle_one_request(self):
552
 
        """Handle a single HTTP request, by abruptly closing the connection"""
553
 
        self.close_connection = 1
554
 
 
555
 
 
556
 
class TestWallServer(TestSpecificRequestHandler):
 
431
class TestWallServer(object):
557
432
    """Tests exceptions during the connection phase"""
558
433
 
559
 
    _req_handler_class = WallRequestHandler
 
434
    def create_transport_readonly_server(self):
 
435
        return HttpServer(WallRequestHandler)
560
436
 
561
437
    def test_http_has(self):
562
438
        server = self.get_readonly_server()
576
452
                          t.get, 'foo/bar')
577
453
 
578
454
 
579
 
class BadStatusRequestHandler(http_server.TestingHTTPRequestHandler):
580
 
    """Whatever request comes in, returns a bad status"""
581
 
 
582
 
    def parse_request(self):
583
 
        """Fakes handling a single HTTP request, returns a bad status"""
584
 
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
585
 
        self.send_response(0, "Bad status")
586
 
        self.close_connection = 1
587
 
        return False
588
 
 
589
 
 
590
 
class TestBadStatusServer(TestSpecificRequestHandler):
 
455
class TestWallServer_urllib(TestWallServer, TestCaseWithWebserver):
 
456
    """Tests "wall" server for urllib implementation"""
 
457
 
 
458
    _transport = HttpTransport_urllib
 
459
 
 
460
 
 
461
class TestWallServer_pycurl(TestWithTransport_pycurl,
 
462
                            TestWallServer,
 
463
                            TestCaseWithWebserver):
 
464
    """Tests "wall" server for pycurl implementation"""
 
465
 
 
466
 
 
467
class TestBadStatusServer(object):
591
468
    """Tests bad status from server."""
592
469
 
593
 
    _req_handler_class = BadStatusRequestHandler
 
470
    def create_transport_readonly_server(self):
 
471
        return HttpServer(BadStatusRequestHandler)
594
472
 
595
473
    def test_http_has(self):
596
474
        server = self.get_readonly_server()
603
481
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
604
482
 
605
483
 
606
 
class InvalidStatusRequestHandler(http_server.TestingHTTPRequestHandler):
607
 
    """Whatever request comes in, returns an invalid status"""
608
 
 
609
 
    def parse_request(self):
610
 
        """Fakes handling a single HTTP request, returns a bad status"""
611
 
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
612
 
        self.wfile.write("Invalid status line\r\n")
613
 
        return False
 
484
class TestBadStatusServer_urllib(TestBadStatusServer, TestCaseWithWebserver):
 
485
    """Tests bad status server for urllib implementation"""
 
486
 
 
487
    _transport = HttpTransport_urllib
 
488
 
 
489
 
 
490
class TestBadStatusServer_pycurl(TestWithTransport_pycurl,
 
491
                                 TestBadStatusServer,
 
492
                                 TestCaseWithWebserver):
 
493
    """Tests bad status server for pycurl implementation"""
614
494
 
615
495
 
616
496
class TestInvalidStatusServer(TestBadStatusServer):
619
499
    Both implementations raises the same error as for a bad status.
620
500
    """
621
501
 
622
 
    _req_handler_class = InvalidStatusRequestHandler
623
 
 
624
 
    def test_http_has(self):
625
 
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
626
 
            raise tests.KnownFailure(
627
 
                'pycurl hangs if the server send back garbage')
628
 
        super(TestInvalidStatusServer, self).test_http_has()
629
 
 
630
 
    def test_http_get(self):
631
 
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
632
 
            raise tests.KnownFailure(
633
 
                'pycurl hangs if the server send back garbage')
634
 
        super(TestInvalidStatusServer, self).test_http_get()
635
 
 
636
 
 
637
 
class BadProtocolRequestHandler(http_server.TestingHTTPRequestHandler):
638
 
    """Whatever request comes in, returns a bad protocol version"""
639
 
 
640
 
    def parse_request(self):
641
 
        """Fakes handling a single HTTP request, returns a bad status"""
642
 
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
643
 
        # Returns an invalid protocol version, but curl just
644
 
        # ignores it and those cannot be tested.
645
 
        self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
646
 
                                           404,
647
 
                                           'Look at my protocol version'))
648
 
        return False
649
 
 
650
 
 
651
 
class TestBadProtocolServer(TestSpecificRequestHandler):
 
502
    def create_transport_readonly_server(self):
 
503
        return HttpServer(InvalidStatusRequestHandler)
 
504
 
 
505
 
 
506
class TestInvalidStatusServer_urllib(TestInvalidStatusServer,
 
507
                                     TestCaseWithWebserver):
 
508
    """Tests invalid status server for urllib implementation"""
 
509
 
 
510
    _transport = HttpTransport_urllib
 
511
 
 
512
 
 
513
class TestInvalidStatusServer_pycurl(TestWithTransport_pycurl,
 
514
                                     TestInvalidStatusServer,
 
515
                                     TestCaseWithWebserver):
 
516
    """Tests invalid status server for pycurl implementation"""
 
517
 
 
518
 
 
519
class TestBadProtocolServer(object):
652
520
    """Tests bad protocol from server."""
653
521
 
654
 
    _req_handler_class = BadProtocolRequestHandler
655
 
 
656
 
    def setUp(self):
657
 
        if pycurl_present and self._transport == PyCurlTransport:
658
 
            raise tests.TestNotApplicable(
659
 
                "pycurl doesn't check the protocol version")
660
 
        super(TestBadProtocolServer, self).setUp()
 
522
    def create_transport_readonly_server(self):
 
523
        return HttpServer(BadProtocolRequestHandler)
661
524
 
662
525
    def test_http_has(self):
663
526
        server = self.get_readonly_server()
670
533
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
671
534
 
672
535
 
673
 
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
674
 
    """Whatever request comes in, returns a 403 code"""
675
 
 
676
 
    def parse_request(self):
677
 
        """Handle a single HTTP request, by replying we cannot handle it"""
678
 
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
679
 
        self.send_error(403)
680
 
        return False
681
 
 
682
 
 
683
 
class TestForbiddenServer(TestSpecificRequestHandler):
 
536
class TestBadProtocolServer_urllib(TestBadProtocolServer,
 
537
                                   TestCaseWithWebserver):
 
538
    """Tests bad protocol server for urllib implementation"""
 
539
 
 
540
    _transport = HttpTransport_urllib
 
541
 
 
542
# curl don't check the protocol version
 
543
#class TestBadProtocolServer_pycurl(TestWithTransport_pycurl,
 
544
#                                   TestBadProtocolServer,
 
545
#                                   TestCaseWithWebserver):
 
546
#    """Tests bad protocol server for pycurl implementation"""
 
547
 
 
548
 
 
549
class TestForbiddenServer(object):
684
550
    """Tests forbidden server"""
685
551
 
686
 
    _req_handler_class = ForbiddenRequestHandler
 
552
    def create_transport_readonly_server(self):
 
553
        return HttpServer(ForbiddenRequestHandler)
687
554
 
688
555
    def test_http_has(self):
689
556
        server = self.get_readonly_server()
696
563
        self.assertRaises(errors.TransportError, t.get, 'foo/bar')
697
564
 
698
565
 
699
 
class TestRecordingServer(tests.TestCase):
 
566
class TestForbiddenServer_urllib(TestForbiddenServer, TestCaseWithWebserver):
 
567
    """Tests forbidden server for urllib implementation"""
 
568
 
 
569
    _transport = HttpTransport_urllib
 
570
 
 
571
 
 
572
class TestForbiddenServer_pycurl(TestWithTransport_pycurl,
 
573
                                 TestForbiddenServer,
 
574
                                 TestCaseWithWebserver):
 
575
    """Tests forbidden server for pycurl implementation"""
 
576
 
 
577
 
 
578
class TestRecordingServer(TestCase):
700
579
 
701
580
    def test_create(self):
702
581
        server = RecordingServer(expect_body_tail=None)
727
606
        self.assertEqual('abc', server.received_bytes)
728
607
 
729
608
 
730
 
class TestRangeRequestServer(TestSpecificRequestHandler):
 
609
class TestRangeRequestServer(object):
731
610
    """Tests readv requests against server.
732
611
 
733
 
    We test against default "normal" server.
 
612
    This MUST be used by daughter classes that also inherit from
 
613
    TestCaseWithWebserver.
 
614
 
 
615
    We can't inherit directly from TestCaseWithWebserver or the
 
616
    test framework will try to create an instance which cannot
 
617
    run, its implementation being incomplete.
734
618
    """
735
619
 
736
620
    def setUp(self):
737
 
        super(TestRangeRequestServer, self).setUp()
 
621
        TestCaseWithWebserver.setUp(self)
738
622
        self.build_tree_contents([('a', '0123456789')],)
739
623
 
740
624
    def test_readv(self):
769
653
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
770
654
                              t.readv, 'a', [(12,2)])
771
655
 
772
 
    def test_readv_multiple_get_requests(self):
773
 
        server = self.get_readonly_server()
774
 
        t = self._transport(server.get_url())
775
 
        # force transport to issue multiple requests
776
 
        t._max_readv_combine = 1
777
 
        t._max_get_ranges = 1
778
 
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
779
 
        self.assertEqual(l[0], (0, '0'))
780
 
        self.assertEqual(l[1], (1, '1'))
781
 
        self.assertEqual(l[2], (3, '34'))
782
 
        self.assertEqual(l[3], (9, '9'))
783
 
        # The server should have issued 4 requests
784
 
        self.assertEqual(4, server.GET_request_nb)
785
 
 
786
 
    def test_readv_get_max_size(self):
787
 
        server = self.get_readonly_server()
788
 
        t = self._transport(server.get_url())
789
 
        # force transport to issue multiple requests by limiting the number of
790
 
        # bytes by request. Note that this apply to coalesced offsets only, a
791
 
        # single range will keep its size even if bigger than the limit.
792
 
        t._get_max_size = 2
793
 
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
794
 
        self.assertEqual(l[0], (0, '0'))
795
 
        self.assertEqual(l[1], (1, '1'))
796
 
        self.assertEqual(l[2], (2, '2345'))
797
 
        self.assertEqual(l[3], (6, '6789'))
798
 
        # The server should have issued 3 requests
799
 
        self.assertEqual(3, server.GET_request_nb)
800
 
 
801
 
    def test_complete_readv_leave_pipe_clean(self):
802
 
        server = self.get_readonly_server()
803
 
        t = self._transport(server.get_url())
804
 
        # force transport to issue multiple requests
805
 
        t._get_max_size = 2
806
 
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
807
 
        # The server should have issued 3 requests
808
 
        self.assertEqual(3, server.GET_request_nb)
809
 
        self.assertEqual('0123456789', t.get_bytes('a'))
810
 
        self.assertEqual(4, server.GET_request_nb)
811
 
 
812
 
    def test_incomplete_readv_leave_pipe_clean(self):
813
 
        server = self.get_readonly_server()
814
 
        t = self._transport(server.get_url())
815
 
        # force transport to issue multiple requests
816
 
        t._get_max_size = 2
817
 
        # Don't collapse readv results into a list so that we leave unread
818
 
        # bytes on the socket
819
 
        ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
820
 
        self.assertEqual((0, '0'), ireadv.next())
821
 
        # The server should have issued one request so far 
822
 
        self.assertEqual(1, server.GET_request_nb)
823
 
        self.assertEqual('0123456789', t.get_bytes('a'))
824
 
        # get_bytes issued an additional request, the readv pending ones are
825
 
        # lost
826
 
        self.assertEqual(2, server.GET_request_nb)
827
 
 
828
 
 
829
 
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
830
 
    """Always reply to range request as if they were single.
831
 
 
832
 
    Don't be explicit about it, just to annoy the clients.
833
 
    """
834
 
 
835
 
    def get_multiple_ranges(self, file, file_size, ranges):
836
 
        """Answer as if it was a single range request and ignores the rest"""
837
 
        (start, end) = ranges[0]
838
 
        return self.get_single_range(file, file_size, start, end)
839
 
 
840
656
 
841
657
class TestSingleRangeRequestServer(TestRangeRequestServer):
842
658
    """Test readv against a server which accept only single range requests"""
843
659
 
844
 
    _req_handler_class = SingleRangeRequestHandler
845
 
 
846
 
 
847
 
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
848
 
    """Only reply to simple range requests, errors out on multiple"""
849
 
 
850
 
    def get_multiple_ranges(self, file, file_size, ranges):
851
 
        """Refuses the multiple ranges request"""
852
 
        if len(ranges) > 1:
853
 
            file.close()
854
 
            self.send_error(416, "Requested range not satisfiable")
855
 
            return
856
 
        (start, end) = ranges[0]
857
 
        return self.get_single_range(file, file_size, start, end)
 
660
    def create_transport_readonly_server(self):
 
661
        return HttpServer(SingleRangeRequestHandler)
 
662
 
 
663
 
 
664
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
 
665
                                          TestCaseWithWebserver):
 
666
    """Tests single range requests accepting server for urllib implementation"""
 
667
 
 
668
    _transport = HttpTransport_urllib
 
669
 
 
670
 
 
671
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
672
                                          TestSingleRangeRequestServer,
 
673
                                          TestCaseWithWebserver):
 
674
    """Tests single range requests accepting server for pycurl implementation"""
858
675
 
859
676
 
860
677
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
861
678
    """Test readv against a server which only accept single range requests"""
862
679
 
863
 
    _req_handler_class = SingleOnlyRangeRequestHandler
864
 
 
865
 
 
866
 
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
867
 
    """Ignore range requests without notice"""
868
 
 
869
 
    def do_GET(self):
870
 
        # Update the statistics
871
 
        self.server.test_case_server.GET_request_nb += 1
872
 
        # Just bypass the range handling done by TestingHTTPRequestHandler
873
 
        return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
 
680
    def create_transport_readonly_server(self):
 
681
        return HttpServer(SingleOnlyRangeRequestHandler)
 
682
 
 
683
 
 
684
class TestSingleOnlyRangeRequestServer_urllib(TestSingleOnlyRangeRequestServer,
 
685
                                              TestCaseWithWebserver):
 
686
    """Tests single range requests accepting server for urllib implementation"""
 
687
 
 
688
    _transport = HttpTransport_urllib
 
689
 
 
690
 
 
691
class TestSingleOnlyRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
692
                                              TestSingleOnlyRangeRequestServer,
 
693
                                              TestCaseWithWebserver):
 
694
    """Tests single range requests accepting server for pycurl implementation"""
874
695
 
875
696
 
876
697
class TestNoRangeRequestServer(TestRangeRequestServer):
877
698
    """Test readv against a server which do not accept range requests"""
878
699
 
879
 
    _req_handler_class = NoRangeRequestHandler
880
 
 
881
 
 
882
 
class MultipleRangeWithoutContentLengthRequestHandler(
883
 
    http_server.TestingHTTPRequestHandler):
884
 
    """Reply to multiple range requests without content length header."""
885
 
 
886
 
    def get_multiple_ranges(self, file, file_size, ranges):
887
 
        self.send_response(206)
888
 
        self.send_header('Accept-Ranges', 'bytes')
889
 
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
890
 
        self.send_header("Content-Type",
891
 
                         "multipart/byteranges; boundary=%s" % boundary)
892
 
        self.end_headers()
893
 
        for (start, end) in ranges:
894
 
            self.wfile.write("--%s\r\n" % boundary)
895
 
            self.send_header("Content-type", 'application/octet-stream')
896
 
            self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
897
 
                                                                  end,
898
 
                                                                  file_size))
899
 
            self.end_headers()
900
 
            self.send_range_content(file, start, end - start + 1)
901
 
        # Final boundary
902
 
        self.wfile.write("--%s\r\n" % boundary)
903
 
 
904
 
 
905
 
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
906
 
 
907
 
    _req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
908
 
 
909
 
 
910
 
class TruncatedMultipleRangeRequestHandler(
911
 
    http_server.TestingHTTPRequestHandler):
912
 
    """Reply to multiple range requests truncating the last ones.
913
 
 
914
 
    This server generates responses whose Content-Length describes all the
915
 
    ranges, but fail to include the last ones leading to client short reads.
916
 
    This has been observed randomly with lighttpd (bug #179368).
 
700
    def create_transport_readonly_server(self):
 
701
        return HttpServer(NoRangeRequestHandler)
 
702
 
 
703
 
 
704
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
 
705
                                      TestCaseWithWebserver):
 
706
    """Tests range requests refusing server for urllib implementation"""
 
707
 
 
708
    _transport = HttpTransport_urllib
 
709
 
 
710
 
 
711
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
712
                               TestNoRangeRequestServer,
 
713
                               TestCaseWithWebserver):
 
714
    """Tests range requests refusing server for pycurl implementation"""
 
715
 
 
716
 
 
717
class TestLimitedRangeRequestServer(object):
 
718
    """Tests readv requests against server that errors out on too much ranges.
 
719
 
 
720
    This MUST be used by daughter classes that also inherit from
 
721
    TestCaseWithWebserver.
 
722
 
 
723
    We can't inherit directly from TestCaseWithWebserver or the
 
724
    test framework will try to create an instance which cannot
 
725
    run, its implementation being incomplete.
917
726
    """
918
727
 
919
 
    _truncated_ranges = 2
920
 
 
921
 
    def get_multiple_ranges(self, file, file_size, ranges):
922
 
        self.send_response(206)
923
 
        self.send_header('Accept-Ranges', 'bytes')
924
 
        boundary = 'tagada'
925
 
        self.send_header('Content-Type',
926
 
                         'multipart/byteranges; boundary=%s' % boundary)
927
 
        boundary_line = '--%s\r\n' % boundary
928
 
        # Calculate the Content-Length
929
 
        content_length = 0
930
 
        for (start, end) in ranges:
931
 
            content_length += len(boundary_line)
932
 
            content_length += self._header_line_length(
933
 
                'Content-type', 'application/octet-stream')
934
 
            content_length += self._header_line_length(
935
 
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
936
 
            content_length += len('\r\n') # end headers
937
 
            content_length += end - start # + 1
938
 
        content_length += len(boundary_line)
939
 
        self.send_header('Content-length', content_length)
940
 
        self.end_headers()
941
 
 
942
 
        # Send the multipart body
943
 
        cur = 0
944
 
        for (start, end) in ranges:
945
 
            self.wfile.write(boundary_line)
946
 
            self.send_header('Content-type', 'application/octet-stream')
947
 
            self.send_header('Content-Range', 'bytes %d-%d/%d'
948
 
                             % (start, end, file_size))
949
 
            self.end_headers()
950
 
            if cur + self._truncated_ranges >= len(ranges):
951
 
                # Abruptly ends the response and close the connection
952
 
                self.close_connection = 1
953
 
                return
954
 
            self.send_range_content(file, start, end - start + 1)
955
 
            cur += 1
956
 
        # No final boundary
957
 
        self.wfile.write(boundary_line)
958
 
 
959
 
 
960
 
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
961
 
 
962
 
    _req_handler_class = TruncatedMultipleRangeRequestHandler
963
 
 
964
 
    def setUp(self):
965
 
        super(TestTruncatedMultipleRangeServer, self).setUp()
966
 
        self.build_tree_contents([('a', '0123456789')],)
967
 
 
968
 
    def test_readv_with_short_reads(self):
969
 
        server = self.get_readonly_server()
970
 
        t = self._transport(server.get_url())
971
 
        # Force separate ranges for each offset
972
 
        t._bytes_to_read_before_seek = 0
973
 
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
974
 
        self.assertEqual((0, '0'), ireadv.next())
975
 
        self.assertEqual((2, '2'), ireadv.next())
976
 
        if not self._testing_pycurl():
977
 
            # Only one request have been issued so far (except for pycurl that
978
 
            # try to read the whole response at once)
979
 
            self.assertEqual(1, server.GET_request_nb)
980
 
        self.assertEqual((4, '45'), ireadv.next())
981
 
        self.assertEqual((9, '9'), ireadv.next())
982
 
        # Both implementations issue 3 requests but:
983
 
        # - urllib does two multiple (4 ranges, then 2 ranges) then a single
984
 
        #   range,
985
 
        # - pycurl does two multiple (4 ranges, 4 ranges) then a single range
986
 
        self.assertEqual(3, server.GET_request_nb)
987
 
        # Finally the client have tried a single range request and stays in
988
 
        # that mode
989
 
        self.assertEqual('single', t._range_hint)
990
 
 
991
 
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
992
 
    """Errors out when range specifiers exceed the limit"""
993
 
 
994
 
    def get_multiple_ranges(self, file, file_size, ranges):
995
 
        """Refuses the multiple ranges request"""
996
 
        tcs = self.server.test_case_server
997
 
        if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
998
 
            file.close()
999
 
            # Emulate apache behavior
1000
 
            self.send_error(400, "Bad Request")
1001
 
            return
1002
 
        return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
1003
 
            self, file, file_size, ranges)
1004
 
 
1005
 
 
1006
 
class LimitedRangeHTTPServer(http_server.HttpServer):
1007
 
    """An HttpServer erroring out on requests with too much range specifiers"""
1008
 
 
1009
 
    def __init__(self, request_handler=LimitedRangeRequestHandler,
1010
 
                 protocol_version=None,
1011
 
                 range_limit=None):
1012
 
        http_server.HttpServer.__init__(self, request_handler,
1013
 
                                        protocol_version=protocol_version)
1014
 
        self.range_limit = range_limit
1015
 
 
1016
 
 
1017
 
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1018
 
    """Tests readv requests against a server erroring out on too much ranges."""
1019
 
 
1020
 
    # Requests with more range specifiers will error out
1021
728
    range_limit = 3
1022
729
 
1023
730
    def create_transport_readonly_server(self):
1024
 
        return LimitedRangeHTTPServer(range_limit=self.range_limit,
1025
 
                                      protocol_version=self._protocol_version)
 
731
        # Requests with more range specifiers will error out
 
732
        return LimitedRangeHTTPServer(range_limit=self.range_limit)
1026
733
 
1027
734
    def get_transport(self):
1028
735
        return self._transport(self.get_readonly_server().get_url())
1029
736
 
1030
737
    def setUp(self):
1031
 
        http_utils.TestCaseWithWebserver.setUp(self)
 
738
        TestCaseWithWebserver.setUp(self)
1032
739
        # We need to manipulate ranges that correspond to real chunks in the
1033
740
        # response, so we build a content appropriately.
1034
 
        filler = ''.join(['abcdefghij' for x in range(102)])
 
741
        filler = ''.join(['abcdefghij' for _ in range(102)])
1035
742
        content = ''.join(['%04d' % v + filler for v in range(16)])
1036
743
        self.build_tree_contents([('a', content)],)
1037
744
 
1042
749
        self.assertEqual(l[1], (1024, '0001'))
1043
750
        self.assertEqual(1, self.get_readonly_server().GET_request_nb)
1044
751
 
1045
 
    def test_more_ranges(self):
 
752
    def test_a_lot_of_ranges(self):
1046
753
        t = self.get_transport()
1047
754
        l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
1048
755
        self.assertEqual(l[0], (0, '0000'))
1050
757
        self.assertEqual(l[2], (4096, '0004'))
1051
758
        self.assertEqual(l[3], (8192, '0008'))
1052
759
        # The server will refuse to serve the first request (too much ranges),
1053
 
        # a second request will succeed.
 
760
        # a second request will succeeds.
1054
761
        self.assertEqual(2, self.get_readonly_server().GET_request_nb)
1055
762
 
1056
763
 
1057
 
class TestHttpProxyWhiteBox(tests.TestCase):
 
764
class TestLimitedRangeRequestServer_urllib(TestLimitedRangeRequestServer,
 
765
                                          TestCaseWithWebserver):
 
766
    """Tests limited range requests server for urllib implementation"""
 
767
 
 
768
    _transport = HttpTransport_urllib
 
769
 
 
770
 
 
771
class TestLimitedRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
772
                                          TestLimitedRangeRequestServer,
 
773
                                          TestCaseWithWebserver):
 
774
    """Tests limited range requests server for pycurl implementation"""
 
775
 
 
776
 
 
777
 
 
778
class TestHttpProxyWhiteBox(TestCase):
1058
779
    """Whitebox test proxy http authorization.
1059
780
 
1060
781
    Only the urllib implementation is tested here.
1061
782
    """
1062
783
 
1063
784
    def setUp(self):
1064
 
        tests.TestCase.setUp(self)
 
785
        TestCase.setUp(self)
1065
786
        self._old_env = {}
1066
787
 
1067
788
    def tearDown(self):
1068
789
        self._restore_env()
1069
 
        tests.TestCase.tearDown(self)
1070
790
 
1071
791
    def _install_env(self, env):
1072
792
        for name, value in env.iteritems():
1077
797
            osutils.set_or_unset_env(name, value)
1078
798
 
1079
799
    def _proxied_request(self):
1080
 
        handler = _urllib2_wrappers.ProxyHandler()
1081
 
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
 
800
        handler = ProxyHandler()
 
801
        request = Request('GET','http://baz/buzzle')
1082
802
        handler.set_proxy(request, 'http')
1083
803
        return request
1084
804
 
1093
813
        self.assertRaises(errors.InvalidURL, self._proxied_request)
1094
814
 
1095
815
 
1096
 
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
 
816
class TestProxyHttpServer(object):
1097
817
    """Tests proxy server.
1098
818
 
 
819
    This MUST be used by daughter classes that also inherit from
 
820
    TestCaseWithTwoWebservers.
 
821
 
 
822
    We can't inherit directly from TestCaseWithTwoWebservers or
 
823
    the test framework will try to create an instance which
 
824
    cannot run, its implementation being incomplete.
 
825
 
1099
826
    Be aware that we do not setup a real proxy here. Instead, we
1100
827
    check that the *connection* goes through the proxy by serving
1101
828
    different content (the faked proxy server append '-proxied'
1106
833
    # test https connections.
1107
834
 
1108
835
    def setUp(self):
1109
 
        super(TestProxyHttpServer, self).setUp()
 
836
        TestCaseWithTwoWebservers.setUp(self)
1110
837
        self.build_tree_contents([('foo', 'contents of foo\n'),
1111
838
                                  ('foo-proxied', 'proxied contents of foo\n')])
1112
839
        # Let's setup some attributes for tests
1113
840
        self.server = self.get_readonly_server()
1114
841
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
1115
 
        if self._testing_pycurl():
1116
 
            # Oh my ! pycurl does not check for the port as part of
1117
 
            # no_proxy :-( So we just test the host part
1118
 
            self.no_proxy_host = 'localhost'
1119
 
        else:
1120
 
            self.no_proxy_host = self.proxy_address
 
842
        self.no_proxy_host = self.proxy_address
1121
843
        # The secondary server is the proxy
1122
844
        self.proxy = self.get_secondary_server()
1123
845
        self.proxy_url = self.proxy.get_url()
1124
846
        self._old_env = {}
1125
847
 
1126
 
    def _testing_pycurl(self):
1127
 
        return pycurl_present and self._transport == PyCurlTransport
1128
 
 
1129
848
    def create_transport_secondary_server(self):
1130
849
        """Creates an http server that will serve files with
1131
850
        '-proxied' appended to their names.
1132
851
        """
1133
 
        return http_utils.ProxyServer(protocol_version=self._protocol_version)
 
852
        return ProxyServer()
1134
853
 
1135
854
    def _install_env(self, env):
1136
855
        for name, value in env.iteritems():
1162
881
        self.proxied_in_env({'http_proxy': self.proxy_url})
1163
882
 
1164
883
    def test_HTTP_PROXY(self):
1165
 
        if self._testing_pycurl():
1166
 
            # pycurl does not check HTTP_PROXY for security reasons
1167
 
            # (for use in a CGI context that we do not care
1168
 
            # about. Should we ?)
1169
 
            raise tests.TestNotApplicable(
1170
 
                'pycurl does not check HTTP_PROXY for security reasons')
1171
884
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
1172
885
 
1173
886
    def test_all_proxy(self):
1181
894
                                 'no_proxy': self.no_proxy_host})
1182
895
 
1183
896
    def test_HTTP_PROXY_with_NO_PROXY(self):
1184
 
        if self._testing_pycurl():
1185
 
            raise tests.TestNotApplicable(
1186
 
                'pycurl does not check HTTP_PROXY for security reasons')
1187
897
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
1188
898
                                 'NO_PROXY': self.no_proxy_host})
1189
899
 
1196
906
                                 'NO_PROXY': self.no_proxy_host})
1197
907
 
1198
908
    def test_http_proxy_without_scheme(self):
1199
 
        if self._testing_pycurl():
1200
 
            # pycurl *ignores* invalid proxy env variables. If that ever change
1201
 
            # in the future, this test will fail indicating that pycurl do not
1202
 
            # ignore anymore such variables.
1203
 
            self.not_proxied_in_env({'http_proxy': self.proxy_address})
1204
 
        else:
1205
 
            self.assertRaises(errors.InvalidURL,
1206
 
                              self.proxied_in_env,
1207
 
                              {'http_proxy': self.proxy_address})
1208
 
 
1209
 
 
1210
 
class TestRanges(http_utils.TestCaseWithWebserver):
1211
 
    """Test the Range header in GET methods."""
1212
 
 
1213
 
    def setUp(self):
1214
 
        http_utils.TestCaseWithWebserver.setUp(self)
 
909
        self.assertRaises(errors.InvalidURL,
 
910
                          self.proxied_in_env,
 
911
                          {'http_proxy': self.proxy_address})
 
912
 
 
913
 
 
914
class TestProxyHttpServer_urllib(TestProxyHttpServer,
 
915
                                 TestCaseWithTwoWebservers):
 
916
    """Tests proxy server for urllib implementation"""
 
917
 
 
918
    _transport = HttpTransport_urllib
 
919
 
 
920
 
 
921
class TestProxyHttpServer_pycurl(TestWithTransport_pycurl,
 
922
                                 TestProxyHttpServer,
 
923
                                 TestCaseWithTwoWebservers):
 
924
    """Tests proxy server for pycurl implementation"""
 
925
 
 
926
    def setUp(self):
 
927
        TestProxyHttpServer.setUp(self)
 
928
        # Oh my ! pycurl does not check for the port as part of
 
929
        # no_proxy :-( So we just test the host part
 
930
        self.no_proxy_host = 'localhost'
 
931
 
 
932
    def test_HTTP_PROXY(self):
 
933
        # pycurl does not check HTTP_PROXY for security reasons
 
934
        # (for use in a CGI context that we do not care
 
935
        # about. Should we ?)
 
936
        raise TestSkipped('pycurl does not check HTTP_PROXY '
 
937
            'for security reasons')
 
938
 
 
939
    def test_HTTP_PROXY_with_NO_PROXY(self):
 
940
        raise TestSkipped('pycurl does not check HTTP_PROXY '
 
941
            'for security reasons')
 
942
 
 
943
    def test_http_proxy_without_scheme(self):
 
944
        # pycurl *ignores* invalid proxy env variables. If that
 
945
        # ever change in the future, this test will fail
 
946
        # indicating that pycurl do not ignore anymore such
 
947
        # variables.
 
948
        self.not_proxied_in_env({'http_proxy': self.proxy_address})
 
949
 
 
950
 
 
951
class TestRanges(object):
 
952
    """Test the Range header in GET methods..
 
953
 
 
954
    This MUST be used by daughter classes that also inherit from
 
955
    TestCaseWithWebserver.
 
956
 
 
957
    We can't inherit directly from TestCaseWithWebserver or the
 
958
    test framework will try to create an instance which cannot
 
959
    run, its implementation being incomplete.
 
960
    """
 
961
 
 
962
    def setUp(self):
 
963
        TestCaseWithWebserver.setUp(self)
1215
964
        self.build_tree_contents([('a', '0123456789')],)
1216
965
        server = self.get_readonly_server()
1217
966
        self.transport = self._transport(server.get_url())
1218
967
 
1219
 
    def create_transport_readonly_server(self):
1220
 
        return http_server.HttpServer(protocol_version=self._protocol_version)
1221
 
 
1222
968
    def _file_contents(self, relpath, ranges):
1223
969
        offsets = [ (start, end - start + 1) for start, end in ranges]
1224
970
        coalesce = self.transport._coalesce_offsets
1232
978
    def _file_tail(self, relpath, tail_amount):
1233
979
        code, data = self.transport._get(relpath, [], tail_amount)
1234
980
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1235
 
        data.seek(-tail_amount, 2)
 
981
        data.seek(-tail_amount + 1, 2)
1236
982
        return data.read(tail_amount)
1237
983
 
1238
984
    def test_range_header(self):
1239
985
        # Valid ranges
1240
986
        map(self.assertEqual,['0', '234'],
1241
987
            list(self._file_contents('a', [(0,0), (2,4)])),)
1242
 
 
1243
 
    def test_range_header_tail(self):
 
988
        # Tail
1244
989
        self.assertEqual('789', self._file_tail('a', 3))
1245
 
 
1246
 
    def test_syntactically_invalid_range_header(self):
1247
 
        self.assertListRaises(errors.InvalidHttpRange,
 
990
        # Syntactically invalid range
 
991
        self.assertListRaises(errors.InvalidRange,
1248
992
                          self._file_contents, 'a', [(4, 3)])
1249
 
 
1250
 
    def test_semantically_invalid_range_header(self):
1251
 
        self.assertListRaises(errors.InvalidHttpRange,
 
993
        # Semantically invalid range
 
994
        self.assertListRaises(errors.InvalidRange,
1252
995
                          self._file_contents, 'a', [(42, 128)])
1253
996
 
1254
997
 
1255
 
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1256
 
    """Test redirection between http servers."""
 
998
class TestRanges_urllib(TestRanges, TestCaseWithWebserver):
 
999
    """Test the Range header in GET methods for urllib implementation"""
 
1000
 
 
1001
    _transport = HttpTransport_urllib
 
1002
 
 
1003
 
 
1004
class TestRanges_pycurl(TestWithTransport_pycurl,
 
1005
                        TestRanges,
 
1006
                        TestCaseWithWebserver):
 
1007
    """Test the Range header in GET methods for pycurl implementation"""
 
1008
 
 
1009
 
 
1010
class TestHTTPRedirections(object):
 
1011
    """Test redirection between http servers.
 
1012
 
 
1013
    This MUST be used by daughter classes that also inherit from
 
1014
    TestCaseWithRedirectedWebserver.
 
1015
 
 
1016
    We can't inherit directly from TestCaseWithTwoWebservers or the
 
1017
    test framework will try to create an instance which cannot
 
1018
    run, its implementation being incomplete. 
 
1019
    """
1257
1020
 
1258
1021
    def create_transport_secondary_server(self):
1259
1022
        """Create the secondary server redirecting to the primary server"""
1260
1023
        new = self.get_readonly_server()
1261
1024
 
1262
 
        redirecting = http_utils.HTTPServerRedirecting(
1263
 
            protocol_version=self._protocol_version)
 
1025
        redirecting = HTTPServerRedirecting()
1264
1026
        redirecting.redirect_to(new.host, new.port)
1265
1027
        return redirecting
1266
1028
 
1286
1048
        self.assertEqual([], bundle.revisions)
1287
1049
 
1288
1050
 
1289
 
class RedirectedRequest(_urllib2_wrappers.Request):
1290
 
    """Request following redirections. """
1291
 
 
1292
 
    init_orig = _urllib2_wrappers.Request.__init__
 
1051
class TestHTTPRedirections_urllib(TestHTTPRedirections,
 
1052
                                  TestCaseWithRedirectedWebserver):
 
1053
    """Tests redirections for urllib implementation"""
 
1054
 
 
1055
    _transport = HttpTransport_urllib
 
1056
 
 
1057
 
 
1058
 
 
1059
class TestHTTPRedirections_pycurl(TestWithTransport_pycurl,
 
1060
                                  TestHTTPRedirections,
 
1061
                                  TestCaseWithRedirectedWebserver):
 
1062
    """Tests redirections for pycurl implementation"""
 
1063
 
 
1064
 
 
1065
class RedirectedRequest(Request):
 
1066
    """Request following redirections"""
 
1067
 
 
1068
    init_orig = Request.__init__
1293
1069
 
1294
1070
    def __init__(self, method, url, *args, **kwargs):
1295
 
        """Constructor.
1296
 
 
1297
 
        """
1298
 
        # Since the tests using this class will replace
1299
 
        # _urllib2_wrappers.Request, we can't just call the base class __init__
1300
 
        # or we'll loop.
1301
1071
        RedirectedRequest.init_orig(self, method, url, args, kwargs)
1302
1072
        self.follow_redirections = True
1303
1073
 
1304
1074
 
1305
 
class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):
1306
 
    """Test redirections.
 
1075
class TestHTTPSilentRedirections_urllib(TestCaseWithRedirectedWebserver):
 
1076
    """Test redirections provided by urllib.
1307
1077
 
1308
1078
    http implementations do not redirect silently anymore (they
1309
1079
    do not redirect at all in fact). The mechanism is still in
1316
1086
    -- vila 20070212
1317
1087
    """
1318
1088
 
 
1089
    _transport = HttpTransport_urllib
 
1090
 
1319
1091
    def setUp(self):
1320
 
        if pycurl_present and self._transport == PyCurlTransport:
1321
 
            raise tests.TestNotApplicable(
1322
 
                "pycurl doesn't redirect silently annymore")
1323
 
        super(TestHTTPSilentRedirections, self).setUp()
 
1092
        super(TestHTTPSilentRedirections_urllib, self).setUp()
1324
1093
        self.setup_redirected_request()
1325
1094
        self.addCleanup(self.cleanup_redirected_request)
1326
1095
        self.build_tree_contents([('a','a'),
1347
1116
 
1348
1117
    def create_transport_secondary_server(self):
1349
1118
        """Create the secondary server, redirections are defined in the tests"""
1350
 
        return http_utils.HTTPServerRedirecting(
1351
 
            protocol_version=self._protocol_version)
 
1119
        return HTTPServerRedirecting()
1352
1120
 
1353
1121
    def test_one_redirection(self):
1354
1122
        t = self.old_transport
1370
1138
                                       self.old_server.port)
1371
1139
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1372
1140
                                       self.new_server.port)
1373
 
        self.old_server.redirections = [
1374
 
            ('/1(.*)', r'%s/2\1' % (old_prefix), 302),
1375
 
            ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
1376
 
            ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
1377
 
            ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1378
 
            ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1379
 
            ]
 
1141
        self.old_server.redirections = \
 
1142
            [('/1(.*)', r'%s/2\1' % (old_prefix), 302),
 
1143
             ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
 
1144
             ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
 
1145
             ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
 
1146
             ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
 
1147
             ]
1380
1148
        self.assertEquals('redirected 5 times',t._perform(req).read())
1381
1149
 
1382
1150
 
1383
 
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
1384
 
    """Test transport.do_catching_redirections."""
 
1151
class TestDoCatchRedirections(TestCaseWithRedirectedWebserver):
 
1152
    """Test transport.do_catching_redirections.
 
1153
 
 
1154
    We arbitrarily choose to use urllib transports
 
1155
    """
 
1156
 
 
1157
    _transport = HttpTransport_urllib
1385
1158
 
1386
1159
    def setUp(self):
1387
1160
        super(TestDoCatchRedirections, self).setUp()
1397
1170
 
1398
1171
        # We use None for redirected so that we fail if redirected
1399
1172
        self.assertEquals('0123456789',
1400
 
                          transport.do_catching_redirections(
1401
 
                self.get_a, t, None).read())
 
1173
                          do_catching_redirections(self.get_a, t, None).read())
1402
1174
 
1403
1175
    def test_one_redirection(self):
1404
1176
        self.redirections = 0
1409
1181
            return self._transport(dir)
1410
1182
 
1411
1183
        self.assertEquals('0123456789',
1412
 
                          transport.do_catching_redirections(
1413
 
                self.get_a, self.old_transport, redirected).read())
 
1184
                          do_catching_redirections(self.get_a,
 
1185
                                                   self.old_transport,
 
1186
                                                   redirected
 
1187
                                                   ).read())
1414
1188
        self.assertEquals(1, self.redirections)
1415
1189
 
1416
1190
    def test_redirection_loop(self):
1421
1195
            # a/a/a
1422
1196
            return self.old_transport.clone(exception.target)
1423
1197
 
1424
 
        self.assertRaises(errors.TooManyRedirections,
1425
 
                          transport.do_catching_redirections,
 
1198
        self.assertRaises(errors.TooManyRedirections, do_catching_redirections,
1426
1199
                          self.get_a, self.old_transport, redirected)
1427
1200
 
1428
1201
 
1429
 
class TestAuth(http_utils.TestCaseWithWebserver):
1430
 
    """Test authentication scheme"""
1431
 
 
1432
 
    _auth_header = 'Authorization'
 
1202
class TestAuth(object):
 
1203
    """Test some authentication scheme specified by daughter class.
 
1204
 
 
1205
    This MUST be used by daughter classes that also inherit from
 
1206
    either TestCaseWithWebserver or TestCaseWithTwoWebservers.
 
1207
    """
 
1208
 
1433
1209
    _password_prompt_prefix = ''
1434
1210
 
1435
1211
    def setUp(self):
1436
 
        super(TestAuth, self).setUp()
1437
 
        self.server = self.get_readonly_server()
 
1212
        """Set up the test environment
 
1213
 
 
1214
        Daughter classes should set up their own environment
 
1215
        (including self.server) and explicitely call this
 
1216
        method. This is needed because we want to reuse the same
 
1217
        tests for proxy and no-proxy accesses which have
 
1218
        different ways of setting self.server.
 
1219
        """
1438
1220
        self.build_tree_contents([('a', 'contents of a\n'),
1439
1221
                                  ('b', 'contents of b\n'),])
1440
1222
 
1441
 
    def create_transport_readonly_server(self):
1442
 
        if self._auth_scheme == 'basic':
1443
 
            server = http_utils.HTTPBasicAuthServer(
1444
 
                protocol_version=self._protocol_version)
1445
 
        else:
1446
 
            if self._auth_scheme != 'digest':
1447
 
                raise AssertionError('Unknown auth scheme: %r'
1448
 
                                     % self._auth_scheme)
1449
 
            server = http_utils.HTTPDigestAuthServer(
1450
 
                protocol_version=self._protocol_version)
1451
 
        return server
1452
 
 
1453
 
    def _testing_pycurl(self):
1454
 
        return pycurl_present and self._transport == PyCurlTransport
1455
 
 
1456
1223
    def get_user_url(self, user=None, password=None):
1457
1224
        """Build an url embedding user and password"""
1458
1225
        url = '%s://' % self.server._url_protocol
1464
1231
        url += '%s:%s/' % (self.server.host, self.server.port)
1465
1232
        return url
1466
1233
 
1467
 
    def get_user_transport(self, user=None, password=None):
1468
 
        return self._transport(self.get_user_url(user, password))
1469
 
 
1470
1234
    def test_no_user(self):
1471
1235
        self.server.add_user('joe', 'foo')
1472
1236
        t = self.get_user_transport()
1506
1270
        self.assertEqual(2, self.server.auth_required_errors)
1507
1271
 
1508
1272
    def test_prompt_for_password(self):
1509
 
        if self._testing_pycurl():
1510
 
            raise tests.TestNotApplicable(
1511
 
                'pycurl cannot prompt, it handles auth by embedding'
1512
 
                ' user:pass in urls only')
1513
 
 
1514
1273
        self.server.add_user('joe', 'foo')
1515
1274
        t = self.get_user_transport('joe', None)
1516
 
        stdout = tests.StringIOWrapper()
1517
 
        ui.ui_factory = tests.TestUIFactory(stdin='foo\n', stdout=stdout)
 
1275
        stdout = StringIOWrapper()
 
1276
        ui.ui_factory = TestUIFactory(stdin='foo\n', stdout=stdout)
1518
1277
        self.assertEqual('contents of a\n',t.get('a').read())
1519
1278
        # stdin should be empty
1520
1279
        self.assertEqual('', ui.ui_factory.stdin.readline())
1538
1297
        self.assertEquals(expected_prompt, actual_prompt)
1539
1298
 
1540
1299
    def test_no_prompt_for_password_when_using_auth_config(self):
1541
 
        if self._testing_pycurl():
1542
 
            raise tests.TestNotApplicable(
1543
 
                'pycurl does not support authentication.conf'
1544
 
                ' since it cannot prompt')
1545
 
 
1546
1300
        user =' joe'
1547
1301
        password = 'foo'
1548
1302
        stdin_content = 'bar\n'  # Not the right password
1549
1303
        self.server.add_user(user, password)
1550
1304
        t = self.get_user_transport(user, None)
1551
 
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1552
 
                                            stdout=tests.StringIOWrapper())
 
1305
        ui.ui_factory = TestUIFactory(stdin=stdin_content,
 
1306
                                      stdout=StringIOWrapper())
1553
1307
        # Create a minimal config file with the right password
1554
1308
        conf = config.AuthenticationConfig()
1555
1309
        conf._get_config().update(
1563
1317
        # Only one 'Authentication Required' error should occur
1564
1318
        self.assertEqual(1, self.server.auth_required_errors)
1565
1319
 
1566
 
    def test_changing_nonce(self):
1567
 
        if self._auth_scheme != 'digest':
1568
 
            raise tests.TestNotApplicable('HTTP auth digest only test')
1569
 
        if self._testing_pycurl():
1570
 
            raise tests.KnownFailure(
1571
 
                'pycurl does not handle a nonce change')
1572
 
        self.server.add_user('joe', 'foo')
1573
 
        t = self.get_user_transport('joe', 'foo')
1574
 
        self.assertEqual('contents of a\n', t.get('a').read())
1575
 
        self.assertEqual('contents of b\n', t.get('b').read())
1576
 
        # Only one 'Authentication Required' error should have
1577
 
        # occured so far
1578
 
        self.assertEqual(1, self.server.auth_required_errors)
1579
 
        # The server invalidates the current nonce
1580
 
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1581
 
        self.assertEqual('contents of a\n', t.get('a').read())
1582
 
        # Two 'Authentication Required' errors should occur (the
1583
 
        # initial 'who are you' and a second 'who are you' with the new nonce)
1584
 
        self.assertEqual(2, self.server.auth_required_errors)
1585
 
 
 
1320
 
 
1321
 
 
1322
class TestHTTPAuth(TestAuth):
 
1323
    """Test HTTP authentication schemes.
 
1324
 
 
1325
    Daughter classes MUST inherit from TestCaseWithWebserver too.
 
1326
    """
 
1327
 
 
1328
    _auth_header = 'Authorization'
 
1329
 
 
1330
    def setUp(self):
 
1331
        TestCaseWithWebserver.setUp(self)
 
1332
        self.server = self.get_readonly_server()
 
1333
        TestAuth.setUp(self)
 
1334
 
 
1335
    def get_user_transport(self, user=None, password=None):
 
1336
        return self._transport(self.get_user_url(user, password))
1586
1337
 
1587
1338
 
1588
1339
class TestProxyAuth(TestAuth):
1589
 
    """Test proxy authentication schemes."""
 
1340
    """Test proxy authentication schemes.
1590
1341
 
 
1342
    Daughter classes MUST also inherit from TestCaseWithWebserver.
 
1343
    """
1591
1344
    _auth_header = 'Proxy-authorization'
1592
 
    _password_prompt_prefix='Proxy '
 
1345
    _password_prompt_prefix = 'Proxy '
 
1346
 
1593
1347
 
1594
1348
    def setUp(self):
1595
 
        super(TestProxyAuth, self).setUp()
 
1349
        TestCaseWithWebserver.setUp(self)
 
1350
        self.server = self.get_readonly_server()
1596
1351
        self._old_env = {}
1597
1352
        self.addCleanup(self._restore_env)
 
1353
        TestAuth.setUp(self)
1598
1354
        # Override the contents to avoid false positives
1599
1355
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
1600
1356
                                  ('b', 'not proxied contents of b\n'),
1602
1358
                                  ('b-proxied', 'contents of b\n'),
1603
1359
                                  ])
1604
1360
 
1605
 
    def create_transport_readonly_server(self):
1606
 
        if self._auth_scheme == 'basic':
1607
 
            server = http_utils.ProxyBasicAuthServer(
1608
 
                protocol_version=self._protocol_version)
1609
 
        else:
1610
 
            if self._auth_scheme != 'digest':
1611
 
                raise AssertionError('Unknown auth scheme: %r'
1612
 
                                     % self._auth_scheme)
1613
 
            server = http_utils.ProxyDigestAuthServer(
1614
 
                protocol_version=self._protocol_version)
1615
 
        return server
1616
 
 
1617
1361
    def get_user_transport(self, user=None, password=None):
1618
1362
        self._install_env({'all_proxy': self.get_user_url(user, password)})
1619
1363
        return self._transport(self.server.get_url())
1626
1370
        for name, value in self._old_env.iteritems():
1627
1371
            osutils.set_or_unset_env(name, value)
1628
1372
 
1629
 
    def test_empty_pass(self):
1630
 
        if self._testing_pycurl():
1631
 
            import pycurl
1632
 
            if pycurl.version_info()[1] < '7.16.0':
1633
 
                raise tests.KnownFailure(
1634
 
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
1635
 
        super(TestProxyAuth, self).test_empty_pass()
1636
 
 
1637
 
 
1638
 
class SampleSocket(object):
1639
 
    """A socket-like object for use in testing the HTTP request handler."""
1640
 
 
1641
 
    def __init__(self, socket_read_content):
1642
 
        """Constructs a sample socket.
1643
 
 
1644
 
        :param socket_read_content: a byte sequence
1645
 
        """
1646
 
        # Use plain python StringIO so we can monkey-patch the close method to
1647
 
        # not discard the contents.
1648
 
        from StringIO import StringIO
1649
 
        self.readfile = StringIO(socket_read_content)
1650
 
        self.writefile = StringIO()
1651
 
        self.writefile.close = lambda: None
1652
 
 
1653
 
    def makefile(self, mode='r', bufsize=None):
1654
 
        if 'r' in mode:
1655
 
            return self.readfile
1656
 
        else:
1657
 
            return self.writefile
1658
 
 
1659
 
 
1660
 
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1661
 
 
1662
 
    def setUp(self):
1663
 
        super(SmartHTTPTunnellingTest, self).setUp()
1664
 
        # We use the VFS layer as part of HTTP tunnelling tests.
1665
 
        self._captureVar('BZR_NO_SMART_VFS', None)
1666
 
        self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1667
 
 
1668
 
    def create_transport_readonly_server(self):
1669
 
        return http_utils.HTTPServerWithSmarts(
1670
 
            protocol_version=self._protocol_version)
1671
 
 
1672
 
    def test_open_bzrdir(self):
1673
 
        branch = self.make_branch('relpath')
1674
 
        http_server = self.get_readonly_server()
1675
 
        url = http_server.get_url() + 'relpath'
1676
 
        bd = bzrdir.BzrDir.open(url)
1677
 
        self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1678
 
 
1679
 
    def test_bulk_data(self):
1680
 
        # We should be able to send and receive bulk data in a single message.
1681
 
        # The 'readv' command in the smart protocol both sends and receives
1682
 
        # bulk data, so we use that.
1683
 
        self.build_tree(['data-file'])
1684
 
        http_server = self.get_readonly_server()
1685
 
        http_transport = self._transport(http_server.get_url())
1686
 
        medium = http_transport.get_smart_medium()
1687
 
        # Since we provide the medium, the url below will be mostly ignored
1688
 
        # during the test, as long as the path is '/'.
1689
 
        remote_transport = remote.RemoteTransport('bzr://fake_host/',
1690
 
                                                  medium=medium)
1691
 
        self.assertEqual(
1692
 
            [(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
1693
 
 
1694
 
    def test_http_send_smart_request(self):
1695
 
 
1696
 
        post_body = 'hello\n'
1697
 
        expected_reply_body = 'ok\x012\n'
1698
 
 
1699
 
        http_server = self.get_readonly_server()
1700
 
        http_transport = self._transport(http_server.get_url())
1701
 
        medium = http_transport.get_smart_medium()
1702
 
        response = medium.send_http_smart_request(post_body)
1703
 
        reply_body = response.read()
1704
 
        self.assertEqual(expected_reply_body, reply_body)
1705
 
 
1706
 
    def test_smart_http_server_post_request_handler(self):
1707
 
        httpd = self.get_readonly_server()._get_httpd()
1708
 
 
1709
 
        socket = SampleSocket(
1710
 
            'POST /.bzr/smart %s \r\n' % self._protocol_version
1711
 
            # HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
1712
 
            # for 1.0)
1713
 
            + 'Content-Length: 6\r\n'
1714
 
            '\r\n'
1715
 
            'hello\n')
1716
 
        # Beware: the ('localhost', 80) below is the
1717
 
        # client_address parameter, but we don't have one because
1718
 
        # we have defined a socket which is not bound to an
1719
 
        # address. The test framework never uses this client
1720
 
        # address, so far...
1721
 
        request_handler = http_utils.SmartRequestHandler(socket,
1722
 
                                                         ('localhost', 80),
1723
 
                                                         httpd)
1724
 
        response = socket.writefile.getvalue()
1725
 
        self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
1726
 
        # This includes the end of the HTTP headers, and all the body.
1727
 
        expected_end_of_response = '\r\n\r\nok\x012\n'
1728
 
        self.assertEndsWith(response, expected_end_of_response)
1729
 
 
1730
 
 
1731
 
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
1732
 
    """No smart server here request handler."""
1733
 
 
1734
 
    def do_POST(self):
1735
 
        self.send_error(403, "Forbidden")
1736
 
 
1737
 
 
1738
 
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
1739
 
    """Test smart client behaviour against an http server without smarts."""
1740
 
 
1741
 
    _req_handler_class = ForbiddenRequestHandler
1742
 
 
1743
 
    def test_probe_smart_server(self):
1744
 
        """Test error handling against server refusing smart requests."""
1745
 
        server = self.get_readonly_server()
1746
 
        t = self._transport(server.get_url())
1747
 
        # No need to build a valid smart request here, the server will not even
1748
 
        # try to interpret it.
1749
 
        self.assertRaises(errors.SmartProtocolError,
1750
 
                          t.send_http_smart_request, 'whatever')
 
1373
 
 
1374
class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
 
1375
    """Test http basic authentication scheme"""
 
1376
 
 
1377
    _transport = HttpTransport_urllib
 
1378
 
 
1379
    def create_transport_readonly_server(self):
 
1380
        return HTTPBasicAuthServer()
 
1381
 
 
1382
 
 
1383
class TestHTTPProxyBasicAuth(TestProxyAuth, TestCaseWithWebserver):
 
1384
    """Test proxy basic authentication scheme"""
 
1385
 
 
1386
    _transport = HttpTransport_urllib
 
1387
 
 
1388
    def create_transport_readonly_server(self):
 
1389
        return ProxyBasicAuthServer()
 
1390
 
 
1391
 
 
1392
class TestDigestAuth(object):
 
1393
    """Digest Authentication specific tests"""
 
1394
 
 
1395
    def test_changing_nonce(self):
 
1396
        self.server.add_user('joe', 'foo')
 
1397
        t = self.get_user_transport('joe', 'foo')
 
1398
        self.assertEqual('contents of a\n', t.get('a').read())
 
1399
        self.assertEqual('contents of b\n', t.get('b').read())
 
1400
        # Only one 'Authentication Required' error should have
 
1401
        # occured so far
 
1402
        self.assertEqual(1, self.server.auth_required_errors)
 
1403
        # The server invalidates the current nonce
 
1404
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
 
1405
        self.assertEqual('contents of a\n', t.get('a').read())
 
1406
        # Two 'Authentication Required' errors should occur (the
 
1407
        # initial 'who are you' and a second 'who are you' with the new nonce)
 
1408
        self.assertEqual(2, self.server.auth_required_errors)
 
1409
 
 
1410
 
 
1411
class TestHTTPDigestAuth(TestHTTPAuth, TestDigestAuth, TestCaseWithWebserver):
 
1412
    """Test http digest authentication scheme"""
 
1413
 
 
1414
    _transport = HttpTransport_urllib
 
1415
 
 
1416
    def create_transport_readonly_server(self):
 
1417
        return HTTPDigestAuthServer()
 
1418
 
 
1419
 
 
1420
class TestHTTPProxyDigestAuth(TestProxyAuth, TestDigestAuth,
 
1421
                              TestCaseWithWebserver):
 
1422
    """Test proxy digest authentication scheme"""
 
1423
 
 
1424
    _transport = HttpTransport_urllib
 
1425
 
 
1426
    def create_transport_readonly_server(self):
 
1427
        return ProxyDigestAuthServer()
1751
1428