~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Vincent Ladeuil
  • Date: 2008-01-03 08:49:38 UTC
  • mfrom: (3111.1.31 175524)
  • mto: This revision was merged to the branch mainline in revision 3158.
  • Revision ID: v.ladeuil+lp@free.fr-20080103084938-7kvurk5uvde2ui54
Fix bug #175524, http test servers are 1.1 compliant

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