~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Vincent Ladeuil
  • Date: 2011-08-20 09:28:27 UTC
  • mfrom: (5050.78.2 2.2)
  • mto: (5609.48.8 2.3)
  • mto: This revision was merged to the branch mainline in revision 6090.
  • Revision ID: v.ladeuil+lp@free.fr-20110820092827-9dyakfslp0r3hb1k
Merge 2.2 into 2.3 (including fix for #614713, #609187 and #812928)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
23
23
# TODO: Should be renamed to bzrlib.transport.http.tests?
24
24
# TODO: What about renaming to bzrlib.tests.transport.http ?
25
25
 
26
 
from cStringIO import StringIO
27
26
import httplib
28
 
import os
29
 
import select
30
27
import SimpleHTTPServer
31
28
import socket
32
29
import sys
42
39
    tests,
43
40
    transport,
44
41
    ui,
45
 
    urlutils,
46
 
    )
47
 
from bzrlib.symbol_versioning import (
48
 
    deprecated_in,
49
42
    )
50
43
from bzrlib.tests import (
 
44
    features,
51
45
    http_server,
52
46
    http_utils,
 
47
    test_server,
 
48
    )
 
49
from bzrlib.tests.scenarios import (
 
50
    load_tests_apply_scenarios,
 
51
    multiply_scenarios,
53
52
    )
54
53
from bzrlib.transport import (
55
54
    http,
61
60
    )
62
61
 
63
62
 
64
 
try:
 
63
if features.pycurl.available():
65
64
    from bzrlib.transport.http._pycurl import PyCurlTransport
66
 
    pycurl_present = True
67
 
except errors.DependencyNotPresent:
68
 
    pycurl_present = False
69
 
 
70
 
 
71
 
def load_tests(standard_tests, module, loader):
72
 
    """Multiply tests for http clients and protocol versions."""
73
 
    result = loader.suiteClass()
74
 
 
75
 
    # one for each transport implementation
76
 
    t_tests, remaining_tests = tests.split_suite_by_condition(
77
 
        standard_tests, tests.condition_isinstance((
78
 
                TestHttpTransportRegistration,
79
 
                TestHttpTransportUrls,
80
 
                Test_redirected_to,
81
 
                )))
 
65
 
 
66
 
 
67
load_tests = load_tests_apply_scenarios
 
68
 
 
69
 
 
70
def vary_by_http_client_implementation():
 
71
    """Test the two libraries we can use, pycurl and urllib."""
82
72
    transport_scenarios = [
83
73
        ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
84
74
                        _server=http_server.HttpServer_urllib,
85
 
                        _qualified_prefix='http+urllib',)),
 
75
                        _url_protocol='http+urllib',)),
86
76
        ]
87
 
    if pycurl_present:
 
77
    if features.pycurl.available():
88
78
        transport_scenarios.append(
89
79
            ('pycurl', dict(_transport=PyCurlTransport,
90
80
                            _server=http_server.HttpServer_PyCurl,
91
 
                            _qualified_prefix='http+pycurl',)))
92
 
    tests.multiply_tests(t_tests, transport_scenarios, result)
93
 
 
94
 
    # each implementation tested with each HTTP version
95
 
    tp_tests, remaining_tests = tests.split_suite_by_condition(
96
 
        remaining_tests, tests.condition_isinstance((
97
 
                SmartHTTPTunnellingTest,
98
 
                TestDoCatchRedirections,
99
 
                TestHTTPConnections,
100
 
                TestHTTPRedirections,
101
 
                TestHTTPSilentRedirections,
102
 
                TestLimitedRangeRequestServer,
103
 
                TestPost,
104
 
                TestProxyHttpServer,
105
 
                TestRanges,
106
 
                TestSpecificRequestHandler,
107
 
                )))
108
 
    protocol_scenarios = [
109
 
            ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
110
 
            ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
111
 
            ]
112
 
    tp_scenarios = tests.multiply_scenarios(transport_scenarios,
113
 
                                            protocol_scenarios)
114
 
    tests.multiply_tests(tp_tests, tp_scenarios, result)
115
 
 
116
 
    # proxy auth: each auth scheme on all http versions on all implementations.
117
 
    tppa_tests, remaining_tests = tests.split_suite_by_condition(
118
 
        remaining_tests, tests.condition_isinstance((
119
 
                TestProxyAuth,
120
 
                )))
121
 
    proxy_auth_scheme_scenarios = [
 
81
                            _url_protocol='http+pycurl',)))
 
82
    return transport_scenarios
 
83
 
 
84
 
 
85
def vary_by_http_protocol_version():
 
86
    """Test on http/1.0 and 1.1"""
 
87
    return [
 
88
        ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
 
89
        ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
 
90
        ]
 
91
 
 
92
 
 
93
def vary_by_http_proxy_auth_scheme():
 
94
    return [
122
95
        ('basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
123
96
        ('digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
124
97
        ('basicdigest',
125
 
         dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
98
            dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
126
99
        ]
127
 
    tppa_scenarios = tests.multiply_scenarios(tp_scenarios,
128
 
                                              proxy_auth_scheme_scenarios)
129
 
    tests.multiply_tests(tppa_tests, tppa_scenarios, result)
130
 
 
131
 
    # auth: each auth scheme on all http versions on all implementations.
132
 
    tpa_tests, remaining_tests = tests.split_suite_by_condition(
133
 
        remaining_tests, tests.condition_isinstance((
134
 
                TestAuth,
135
 
                )))
136
 
    auth_scheme_scenarios = [
 
100
 
 
101
 
 
102
def vary_by_http_auth_scheme():
 
103
    return [
137
104
        ('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
138
105
        ('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
139
106
        ('basicdigest',
140
 
         dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
 
107
            dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
141
108
        ]
142
 
    tpa_scenarios = tests.multiply_scenarios(tp_scenarios,
143
 
                                             auth_scheme_scenarios)
144
 
    tests.multiply_tests(tpa_tests, tpa_scenarios, result)
145
 
 
146
 
    # activity: on all http[s] versions on all implementations
147
 
    tpact_tests, remaining_tests = tests.split_suite_by_condition(
148
 
        remaining_tests, tests.condition_isinstance((
149
 
                TestActivity,
150
 
                )))
 
109
 
 
110
 
 
111
def vary_by_http_activity():
151
112
    activity_scenarios = [
152
113
        ('urllib,http', dict(_activity_server=ActivityHTTPServer,
153
 
                             _transport=_urllib.HttpTransport_urllib,)),
 
114
                            _transport=_urllib.HttpTransport_urllib,)),
154
115
        ]
155
116
    if tests.HTTPSServerFeature.available():
156
117
        activity_scenarios.append(
157
118
            ('urllib,https', dict(_activity_server=ActivityHTTPSServer,
158
 
                                  _transport=_urllib.HttpTransport_urllib,)),)
159
 
    if pycurl_present:
 
119
                                _transport=_urllib.HttpTransport_urllib,)),)
 
120
    if features.pycurl.available():
160
121
        activity_scenarios.append(
161
122
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
162
 
                                 _transport=PyCurlTransport,)),)
 
123
                                _transport=PyCurlTransport,)),)
163
124
        if tests.HTTPSServerFeature.available():
164
125
            from bzrlib.tests import (
165
126
                ssl_certs,
177
138
 
178
139
            activity_scenarios.append(
179
140
                ('pycurl,https', dict(_activity_server=ActivityHTTPSServer,
180
 
                                      _transport=HTTPS_pycurl_transport,)),)
181
 
 
182
 
    tpact_scenarios = tests.multiply_scenarios(activity_scenarios,
183
 
                                               protocol_scenarios)
184
 
    tests.multiply_tests(tpact_tests, tpact_scenarios, result)
185
 
 
186
 
    # No parametrization for the remaining tests
187
 
    result.addTests(remaining_tests)
188
 
 
189
 
    return result
 
141
                                    _transport=HTTPS_pycurl_transport,)),)
 
142
    return activity_scenarios
190
143
 
191
144
 
192
145
class FakeManager(object):
204
157
    It records the bytes sent to it, and replies with a 200.
205
158
    """
206
159
 
207
 
    def __init__(self, expect_body_tail=None):
 
160
    def __init__(self, expect_body_tail=None, scheme=''):
208
161
        """Constructor.
209
162
 
210
163
        :type expect_body_tail: str
215
168
        self.host = None
216
169
        self.port = None
217
170
        self.received_bytes = ''
218
 
 
219
 
    def setUp(self):
 
171
        self.scheme = scheme
 
172
 
 
173
    def get_url(self):
 
174
        return '%s://%s:%s/' % (self.scheme, self.host, self.port)
 
175
 
 
176
    def start_server(self):
220
177
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
221
178
        self._sock.bind(('127.0.0.1', 0))
222
179
        self.host, self.port = self._sock.getsockname()
223
180
        self._ready = threading.Event()
224
 
        self._thread = threading.Thread(target=self._accept_read_and_reply)
225
 
        self._thread.setDaemon(True)
 
181
        self._thread = test_server.ThreadWithException(
 
182
            event=self._ready, target=self._accept_read_and_reply)
226
183
        self._thread.start()
227
 
        self._ready.wait(5)
 
184
        if 'threads' in tests.selftest_debug_flags:
 
185
            sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
 
186
        self._ready.wait()
228
187
 
229
188
    def _accept_read_and_reply(self):
230
189
        self._sock.listen(1)
231
190
        self._ready.set()
232
 
        self._sock.settimeout(5)
233
 
        try:
234
 
            conn, address = self._sock.accept()
235
 
            # On win32, the accepted connection will be non-blocking to start
236
 
            # with because we're using settimeout.
237
 
            conn.setblocking(True)
 
191
        conn, address = self._sock.accept()
 
192
        if self._expect_body_tail is not None:
238
193
            while not self.received_bytes.endswith(self._expect_body_tail):
239
194
                self.received_bytes += conn.recv(4096)
240
195
            conn.sendall('HTTP/1.1 200 OK\r\n')
241
 
        except socket.timeout:
242
 
            # Make sure the client isn't stuck waiting for us to e.g. accept.
 
196
        try:
243
197
            self._sock.close()
244
198
        except socket.error:
245
199
            # The client may have already closed the socket.
246
200
            pass
247
201
 
248
 
    def tearDown(self):
 
202
    def stop_server(self):
249
203
        try:
250
 
            self._sock.close()
 
204
            # Issue a fake connection to wake up the server and allow it to
 
205
            # finish quickly
 
206
            fake_conn = osutils.connect_socket((self.host, self.port))
 
207
            fake_conn.close()
251
208
        except socket.error:
252
209
            # We might have already closed it.  We don't care.
253
210
            pass
254
211
        self.host = None
255
212
        self.port = None
 
213
        self._thread.join()
 
214
        if 'threads' in tests.selftest_debug_flags:
 
215
            sys.stderr.write('Thread  joined: %s\n' % (self._thread.ident,))
256
216
 
257
217
 
258
218
class TestAuthHeader(tests.TestCase):
265
225
 
266
226
    def test_empty_header(self):
267
227
        scheme, remainder = self.parse_header('')
268
 
        self.assertEquals('', scheme)
 
228
        self.assertEqual('', scheme)
269
229
        self.assertIs(None, remainder)
270
230
 
271
231
    def test_negotiate_header(self):
272
232
        scheme, remainder = self.parse_header('Negotiate')
273
 
        self.assertEquals('negotiate', scheme)
 
233
        self.assertEqual('negotiate', scheme)
274
234
        self.assertIs(None, remainder)
275
235
 
276
236
    def test_basic_header(self):
277
237
        scheme, remainder = self.parse_header(
278
238
            'Basic realm="Thou should not pass"')
279
 
        self.assertEquals('basic', scheme)
280
 
        self.assertEquals('realm="Thou should not pass"', remainder)
 
239
        self.assertEqual('basic', scheme)
 
240
        self.assertEqual('realm="Thou should not pass"', remainder)
281
241
 
282
242
    def test_basic_extract_realm(self):
283
243
        scheme, remainder = self.parse_header(
285
245
            _urllib2_wrappers.BasicAuthHandler)
286
246
        match, realm = self.auth_handler.extract_realm(remainder)
287
247
        self.assertTrue(match is not None)
288
 
        self.assertEquals('Thou should not pass', realm)
 
248
        self.assertEqual('Thou should not pass', realm)
289
249
 
290
250
    def test_digest_header(self):
291
251
        scheme, remainder = self.parse_header(
292
252
            'Digest realm="Thou should not pass"')
293
 
        self.assertEquals('digest', scheme)
294
 
        self.assertEquals('realm="Thou should not pass"', remainder)
 
253
        self.assertEqual('digest', scheme)
 
254
        self.assertEqual('realm="Thou should not pass"', remainder)
295
255
 
296
256
 
297
257
class TestHTTPServer(tests.TestCase):
302
262
 
303
263
            protocol_version = 'HTTP/0.1'
304
264
 
305
 
        server = http_server.HttpServer(BogusRequestHandler)
306
 
        try:
307
 
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
308
 
        except:
309
 
            server.tearDown()
310
 
            self.fail('HTTP Server creation did not raise UnknownProtocol')
 
265
        self.assertRaises(httplib.UnknownProtocol,
 
266
                          http_server.HttpServer, BogusRequestHandler)
311
267
 
312
268
    def test_force_invalid_protocol(self):
313
 
        server = http_server.HttpServer(protocol_version='HTTP/0.1')
314
 
        try:
315
 
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
316
 
        except:
317
 
            server.tearDown()
318
 
            self.fail('HTTP Server creation did not raise UnknownProtocol')
 
269
        self.assertRaises(httplib.UnknownProtocol,
 
270
                          http_server.HttpServer, protocol_version='HTTP/0.1')
319
271
 
320
272
    def test_server_start_and_stop(self):
321
273
        server = http_server.HttpServer()
322
 
        server.setUp()
323
 
        self.assertTrue(server._http_running)
324
 
        server.tearDown()
325
 
        self.assertFalse(server._http_running)
 
274
        self.addCleanup(server.stop_server)
 
275
        server.start_server()
 
276
        self.assertTrue(server.server is not None)
 
277
        self.assertTrue(server.server.serving is not None)
 
278
        self.assertTrue(server.server.serving)
326
279
 
327
280
    def test_create_http_server_one_zero(self):
328
281
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
330
283
            protocol_version = 'HTTP/1.0'
331
284
 
332
285
        server = http_server.HttpServer(RequestHandlerOneZero)
333
 
        server.setUp()
334
 
        self.addCleanup(server.tearDown)
335
 
        self.assertIsInstance(server._httpd, http_server.TestingHTTPServer)
 
286
        self.start_server(server)
 
287
        self.assertIsInstance(server.server, http_server.TestingHTTPServer)
336
288
 
337
289
    def test_create_http_server_one_one(self):
338
290
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
340
292
            protocol_version = 'HTTP/1.1'
341
293
 
342
294
        server = http_server.HttpServer(RequestHandlerOneOne)
343
 
        server.setUp()
344
 
        self.addCleanup(server.tearDown)
345
 
        self.assertIsInstance(server._httpd,
 
295
        self.start_server(server)
 
296
        self.assertIsInstance(server.server,
346
297
                              http_server.TestingThreadingHTTPServer)
347
298
 
348
299
    def test_create_http_server_force_one_one(self):
352
303
 
353
304
        server = http_server.HttpServer(RequestHandlerOneZero,
354
305
                                        protocol_version='HTTP/1.1')
355
 
        server.setUp()
356
 
        self.addCleanup(server.tearDown)
357
 
        self.assertIsInstance(server._httpd,
 
306
        self.start_server(server)
 
307
        self.assertIsInstance(server.server,
358
308
                              http_server.TestingThreadingHTTPServer)
359
309
 
360
310
    def test_create_http_server_force_one_zero(self):
364
314
 
365
315
        server = http_server.HttpServer(RequestHandlerOneOne,
366
316
                                        protocol_version='HTTP/1.0')
367
 
        server.setUp()
368
 
        self.addCleanup(server.tearDown)
369
 
        self.assertIsInstance(server._httpd,
 
317
        self.start_server(server)
 
318
        self.assertIsInstance(server.server,
370
319
                              http_server.TestingHTTPServer)
371
320
 
372
321
 
374
323
    """Test case to inherit from if pycurl is present"""
375
324
 
376
325
    def _get_pycurl_maybe(self):
377
 
        try:
378
 
            from bzrlib.transport.http._pycurl import PyCurlTransport
379
 
            return PyCurlTransport
380
 
        except errors.DependencyNotPresent:
381
 
            raise tests.TestSkipped('pycurl not present')
 
326
        self.requireFeature(features.pycurl)
 
327
        return PyCurlTransport
382
328
 
383
329
    _transport = property(_get_pycurl_maybe)
384
330
 
391
337
    def test_url_parsing(self):
392
338
        f = FakeManager()
393
339
        url = http.extract_auth('http://example.com', f)
394
 
        self.assertEquals('http://example.com', url)
395
 
        self.assertEquals(0, len(f.credentials))
 
340
        self.assertEqual('http://example.com', url)
 
341
        self.assertEqual(0, len(f.credentials))
396
342
        url = http.extract_auth(
397
 
            'http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
398
 
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
399
 
        self.assertEquals(1, len(f.credentials))
400
 
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
401
 
                          f.credentials[0])
 
343
            'http://user:pass@example.com/bzr/bzr.dev', f)
 
344
        self.assertEqual('http://example.com/bzr/bzr.dev', url)
 
345
        self.assertEqual(1, len(f.credentials))
 
346
        self.assertEqual([None, 'example.com', 'user', 'pass'],
 
347
                         f.credentials[0])
402
348
 
403
349
 
404
350
class TestHttpTransportUrls(tests.TestCase):
405
351
    """Test the http urls."""
406
352
 
 
353
    scenarios = vary_by_http_client_implementation()
 
354
 
407
355
    def test_abs_url(self):
408
356
        """Construction of absolute http URLs"""
409
 
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
357
        t = self._transport('http://example.com/bzr/bzr.dev/')
410
358
        eq = self.assertEqualDiff
411
 
        eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
412
 
        eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
413
 
        eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
 
359
        eq(t.abspath('.'), 'http://example.com/bzr/bzr.dev')
 
360
        eq(t.abspath('foo/bar'), 'http://example.com/bzr/bzr.dev/foo/bar')
 
361
        eq(t.abspath('.bzr'), 'http://example.com/bzr/bzr.dev/.bzr')
414
362
        eq(t.abspath('.bzr/1//2/./3'),
415
 
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
 
363
           'http://example.com/bzr/bzr.dev/.bzr/1/2/3')
416
364
 
417
365
    def test_invalid_http_urls(self):
418
366
        """Trap invalid construction of urls"""
419
 
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
367
        self._transport('http://example.com/bzr/bzr.dev/')
420
368
        self.assertRaises(errors.InvalidURL,
421
369
                          self._transport,
422
 
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
 
370
                          'http://http://example.com/bzr/bzr.dev/')
423
371
 
424
372
    def test_http_root_urls(self):
425
373
        """Construction of URLs from server root"""
426
 
        t = self._transport('http://bzr.ozlabs.org/')
 
374
        t = self._transport('http://example.com/')
427
375
        eq = self.assertEqualDiff
428
376
        eq(t.abspath('.bzr/tree-version'),
429
 
           'http://bzr.ozlabs.org/.bzr/tree-version')
 
377
           'http://example.com/.bzr/tree-version')
430
378
 
431
379
    def test_http_impl_urls(self):
432
380
        """There are servers which ask for particular clients to connect"""
433
381
        server = self._server()
 
382
        server.start_server()
434
383
        try:
435
 
            server.setUp()
436
384
            url = server.get_url()
437
 
            self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
 
385
            self.assertTrue(url.startswith('%s://' % self._url_protocol))
438
386
        finally:
439
 
            server.tearDown()
 
387
            server.stop_server()
440
388
 
441
389
 
442
390
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
451
399
        https by supplying a fake version_info that do not
452
400
        support it.
453
401
        """
454
 
        try:
455
 
            import pycurl
456
 
        except ImportError:
457
 
            raise tests.TestSkipped('pycurl not present')
 
402
        self.requireFeature(features.pycurl)
 
403
        # Import the module locally now that we now it's available.
 
404
        pycurl = features.pycurl.module
458
405
 
459
 
        version_info_orig = pycurl.version_info
460
 
        try:
461
 
            # Now that we have pycurl imported, we can fake its version_info
462
 
            # This was taken from a windows pycurl without SSL
463
 
            # (thanks to bialix)
464
 
            pycurl.version_info = lambda : (2,
465
 
                                            '7.13.2',
466
 
                                            462082,
467
 
                                            'i386-pc-win32',
468
 
                                            2576,
469
 
                                            None,
470
 
                                            0,
471
 
                                            None,
472
 
                                            ('ftp', 'gopher', 'telnet',
473
 
                                             'dict', 'ldap', 'http', 'file'),
474
 
                                            None,
475
 
                                            0,
476
 
                                            None)
477
 
            self.assertRaises(errors.DependencyNotPresent, self._transport,
478
 
                              'https://launchpad.net')
479
 
        finally:
480
 
            # Restore the right function
481
 
            pycurl.version_info = version_info_orig
 
406
        self.overrideAttr(pycurl, 'version_info',
 
407
                          # Fake the pycurl version_info This was taken from
 
408
                          # a windows pycurl without SSL (thanks to bialix)
 
409
                          lambda : (2,
 
410
                                    '7.13.2',
 
411
                                    462082,
 
412
                                    'i386-pc-win32',
 
413
                                    2576,
 
414
                                    None,
 
415
                                    0,
 
416
                                    None,
 
417
                                    ('ftp', 'gopher', 'telnet',
 
418
                                     'dict', 'ldap', 'http', 'file'),
 
419
                                    None,
 
420
                                    0,
 
421
                                    None))
 
422
        self.assertRaises(errors.DependencyNotPresent, self._transport,
 
423
                          'https://launchpad.net')
482
424
 
483
425
 
484
426
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
485
427
    """Test the http connections."""
486
428
 
 
429
    scenarios = multiply_scenarios(
 
430
        vary_by_http_client_implementation(), 
 
431
        vary_by_http_protocol_version(),
 
432
        )
 
433
 
487
434
    def setUp(self):
488
435
        http_utils.TestCaseWithWebserver.setUp(self)
489
436
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
491
438
 
492
439
    def test_http_has(self):
493
440
        server = self.get_readonly_server()
494
 
        t = self._transport(server.get_url())
 
441
        t = self.get_readonly_transport()
495
442
        self.assertEqual(t.has('foo/bar'), True)
496
443
        self.assertEqual(len(server.logs), 1)
497
444
        self.assertContainsRe(server.logs[0],
499
446
 
500
447
    def test_http_has_not_found(self):
501
448
        server = self.get_readonly_server()
502
 
        t = self._transport(server.get_url())
 
449
        t = self.get_readonly_transport()
503
450
        self.assertEqual(t.has('not-found'), False)
504
451
        self.assertContainsRe(server.logs[1],
505
452
            r'"HEAD /not-found HTTP/1.." 404 - "-" "bzr/')
506
453
 
507
454
    def test_http_get(self):
508
455
        server = self.get_readonly_server()
509
 
        t = self._transport(server.get_url())
 
456
        t = self.get_readonly_transport()
510
457
        fp = t.get('foo/bar')
511
458
        self.assertEqualDiff(
512
459
            fp.read(),
534
481
class TestHttpTransportRegistration(tests.TestCase):
535
482
    """Test registrations of various http implementations"""
536
483
 
 
484
    scenarios = vary_by_http_client_implementation()
 
485
 
537
486
    def test_http_registered(self):
538
 
        t = transport.get_transport('%s://foo.com/' % self._qualified_prefix)
 
487
        t = transport.get_transport('%s://foo.com/' % self._url_protocol)
539
488
        self.assertIsInstance(t, transport.Transport)
540
489
        self.assertIsInstance(t, self._transport)
541
490
 
542
491
 
543
492
class TestPost(tests.TestCase):
544
493
 
 
494
    scenarios = multiply_scenarios(
 
495
        vary_by_http_client_implementation(), 
 
496
        vary_by_http_protocol_version(),
 
497
        )
 
498
 
545
499
    def test_post_body_is_received(self):
546
 
        server = RecordingServer(expect_body_tail='end-of-body')
547
 
        server.setUp()
548
 
        self.addCleanup(server.tearDown)
549
 
        scheme = self._qualified_prefix
550
 
        url = '%s://%s:%s/' % (scheme, server.host, server.port)
551
 
        http_transport = self._transport(url)
 
500
        server = RecordingServer(expect_body_tail='end-of-body',
 
501
                                 scheme=self._url_protocol)
 
502
        self.start_server(server)
 
503
        url = server.get_url()
 
504
        # FIXME: needs a cleanup -- vila 20100611
 
505
        http_transport = transport.get_transport(url)
552
506
        code, response = http_transport._post('abc def end-of-body')
553
507
        self.assertTrue(
554
508
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
555
509
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
 
510
        self.assertTrue('content-type: application/octet-stream\r'
 
511
                        in server.received_bytes.lower())
556
512
        # The transport should not be assuming that the server can accept
557
513
        # chunked encoding the first time it connects, because HTTP/1.1, so we
558
514
        # check for the literal string.
594
550
    Daughter classes are expected to override _req_handler_class
595
551
    """
596
552
 
 
553
    scenarios = multiply_scenarios(
 
554
        vary_by_http_client_implementation(), 
 
555
        vary_by_http_protocol_version(),
 
556
        )
 
557
 
597
558
    # Provide a useful default
598
559
    _req_handler_class = http_server.TestingHTTPRequestHandler
599
560
 
600
561
    def create_transport_readonly_server(self):
601
 
        return http_server.HttpServer(self._req_handler_class,
602
 
                                      protocol_version=self._protocol_version)
 
562
        server = http_server.HttpServer(self._req_handler_class,
 
563
                                        protocol_version=self._protocol_version)
 
564
        server._url_protocol = self._url_protocol
 
565
        return server
603
566
 
604
567
    def _testing_pycurl(self):
605
 
        return pycurl_present and self._transport == PyCurlTransport
 
568
        # TODO: This is duplicated for lots of the classes in this file
 
569
        return (features.pycurl.available()
 
570
                and self._transport == PyCurlTransport)
606
571
 
607
572
 
608
573
class WallRequestHandler(http_server.TestingHTTPRequestHandler):
609
574
    """Whatever request comes in, close the connection"""
610
575
 
611
 
    def handle_one_request(self):
 
576
    def _handle_one_request(self):
612
577
        """Handle a single HTTP request, by abruptly closing the connection"""
613
578
        self.close_connection = 1
614
579
 
619
584
    _req_handler_class = WallRequestHandler
620
585
 
621
586
    def test_http_has(self):
622
 
        server = self.get_readonly_server()
623
 
        t = self._transport(server.get_url())
 
587
        t = self.get_readonly_transport()
624
588
        # Unfortunately httplib (see HTTPResponse._read_status
625
589
        # for details) make no distinction between a closed
626
590
        # socket and badly formatted status line, so we can't
627
591
        # just test for ConnectionError, we have to test
628
 
        # InvalidHttpResponse too.
629
 
        self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
 
592
        # InvalidHttpResponse too. And pycurl may raise ConnectionReset
 
593
        # instead of ConnectionError too.
 
594
        self.assertRaises(( errors.ConnectionError, errors.ConnectionReset,
 
595
                            errors.InvalidHttpResponse),
630
596
                          t.has, 'foo/bar')
631
597
 
632
598
    def test_http_get(self):
633
 
        server = self.get_readonly_server()
634
 
        t = self._transport(server.get_url())
635
 
        self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
 
599
        t = self.get_readonly_transport()
 
600
        self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
 
601
                           errors.InvalidHttpResponse),
636
602
                          t.get, 'foo/bar')
637
603
 
638
604
 
653
619
    _req_handler_class = BadStatusRequestHandler
654
620
 
655
621
    def test_http_has(self):
656
 
        server = self.get_readonly_server()
657
 
        t = self._transport(server.get_url())
 
622
        t = self.get_readonly_transport()
658
623
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
659
624
 
660
625
    def test_http_get(self):
661
 
        server = self.get_readonly_server()
662
 
        t = self._transport(server.get_url())
 
626
        t = self.get_readonly_transport()
663
627
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
664
628
 
665
629
 
670
634
        """Fakes handling a single HTTP request, returns a bad status"""
671
635
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
672
636
        self.wfile.write("Invalid status line\r\n")
 
637
        # If we don't close the connection pycurl will hang. Since this is a
 
638
        # stress test we don't *have* to respect the protocol, but we don't
 
639
        # have to sabotage it too much either.
 
640
        self.close_connection = True
673
641
        return False
674
642
 
675
643
 
681
649
 
682
650
    _req_handler_class = InvalidStatusRequestHandler
683
651
 
684
 
    def test_http_has(self):
685
 
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
686
 
            raise tests.KnownFailure(
687
 
                'pycurl hangs if the server send back garbage')
688
 
        super(TestInvalidStatusServer, self).test_http_has()
689
 
 
690
 
    def test_http_get(self):
691
 
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
692
 
            raise tests.KnownFailure(
693
 
                'pycurl hangs if the server send back garbage')
694
 
        super(TestInvalidStatusServer, self).test_http_get()
695
 
 
696
652
 
697
653
class BadProtocolRequestHandler(http_server.TestingHTTPRequestHandler):
698
654
    """Whatever request comes in, returns a bad protocol version"""
714
670
    _req_handler_class = BadProtocolRequestHandler
715
671
 
716
672
    def setUp(self):
717
 
        if pycurl_present and self._transport == PyCurlTransport:
 
673
        if self._testing_pycurl():
718
674
            raise tests.TestNotApplicable(
719
675
                "pycurl doesn't check the protocol version")
720
676
        super(TestBadProtocolServer, self).setUp()
721
677
 
722
678
    def test_http_has(self):
723
 
        server = self.get_readonly_server()
724
 
        t = self._transport(server.get_url())
 
679
        t = self.get_readonly_transport()
725
680
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
726
681
 
727
682
    def test_http_get(self):
728
 
        server = self.get_readonly_server()
729
 
        t = self._transport(server.get_url())
 
683
        t = self.get_readonly_transport()
730
684
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
731
685
 
732
686
 
746
700
    _req_handler_class = ForbiddenRequestHandler
747
701
 
748
702
    def test_http_has(self):
749
 
        server = self.get_readonly_server()
750
 
        t = self._transport(server.get_url())
 
703
        t = self.get_readonly_transport()
751
704
        self.assertRaises(errors.TransportError, t.has, 'foo/bar')
752
705
 
753
706
    def test_http_get(self):
754
 
        server = self.get_readonly_server()
755
 
        t = self._transport(server.get_url())
 
707
        t = self.get_readonly_transport()
756
708
        self.assertRaises(errors.TransportError, t.get, 'foo/bar')
757
709
 
758
710
 
764
716
        self.assertEqual(None, server.host)
765
717
        self.assertEqual(None, server.port)
766
718
 
767
 
    def test_setUp_and_tearDown(self):
 
719
    def test_setUp_and_stop(self):
768
720
        server = RecordingServer(expect_body_tail=None)
769
 
        server.setUp()
 
721
        server.start_server()
770
722
        try:
771
723
            self.assertNotEqual(None, server.host)
772
724
            self.assertNotEqual(None, server.port)
773
725
        finally:
774
 
            server.tearDown()
 
726
            server.stop_server()
775
727
        self.assertEqual(None, server.host)
776
728
        self.assertEqual(None, server.port)
777
729
 
778
730
    def test_send_receive_bytes(self):
779
 
        server = RecordingServer(expect_body_tail='c')
780
 
        server.setUp()
781
 
        self.addCleanup(server.tearDown)
 
731
        server = RecordingServer(expect_body_tail='c', scheme='http')
 
732
        self.start_server(server)
782
733
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
783
734
        sock.connect((server.host, server.port))
784
735
        sock.sendall('abc')
798
749
        self.build_tree_contents([('a', '0123456789')],)
799
750
 
800
751
    def test_readv(self):
801
 
        server = self.get_readonly_server()
802
 
        t = self._transport(server.get_url())
 
752
        t = self.get_readonly_transport()
803
753
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
804
754
        self.assertEqual(l[0], (0, '0'))
805
755
        self.assertEqual(l[1], (1, '1'))
807
757
        self.assertEqual(l[3], (9, '9'))
808
758
 
809
759
    def test_readv_out_of_order(self):
810
 
        server = self.get_readonly_server()
811
 
        t = self._transport(server.get_url())
 
760
        t = self.get_readonly_transport()
812
761
        l = list(t.readv('a', ((1, 1), (9, 1), (0, 1), (3, 2))))
813
762
        self.assertEqual(l[0], (1, '1'))
814
763
        self.assertEqual(l[1], (9, '9'))
816
765
        self.assertEqual(l[3], (3, '34'))
817
766
 
818
767
    def test_readv_invalid_ranges(self):
819
 
        server = self.get_readonly_server()
820
 
        t = self._transport(server.get_url())
 
768
        t = self.get_readonly_transport()
821
769
 
822
770
        # This is intentionally reading off the end of the file
823
771
        # since we are sure that it cannot get there
831
779
 
832
780
    def test_readv_multiple_get_requests(self):
833
781
        server = self.get_readonly_server()
834
 
        t = self._transport(server.get_url())
 
782
        t = self.get_readonly_transport()
835
783
        # force transport to issue multiple requests
836
784
        t._max_readv_combine = 1
837
785
        t._max_get_ranges = 1
845
793
 
846
794
    def test_readv_get_max_size(self):
847
795
        server = self.get_readonly_server()
848
 
        t = self._transport(server.get_url())
 
796
        t = self.get_readonly_transport()
849
797
        # force transport to issue multiple requests by limiting the number of
850
798
        # bytes by request. Note that this apply to coalesced offsets only, a
851
799
        # single range will keep its size even if bigger than the limit.
860
808
 
861
809
    def test_complete_readv_leave_pipe_clean(self):
862
810
        server = self.get_readonly_server()
863
 
        t = self._transport(server.get_url())
 
811
        t = self.get_readonly_transport()
864
812
        # force transport to issue multiple requests
865
813
        t._get_max_size = 2
866
 
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
814
        list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
867
815
        # The server should have issued 3 requests
868
816
        self.assertEqual(3, server.GET_request_nb)
869
817
        self.assertEqual('0123456789', t.get_bytes('a'))
871
819
 
872
820
    def test_incomplete_readv_leave_pipe_clean(self):
873
821
        server = self.get_readonly_server()
874
 
        t = self._transport(server.get_url())
 
822
        t = self.get_readonly_transport()
875
823
        # force transport to issue multiple requests
876
824
        t._get_max_size = 2
877
825
        # Don't collapse readv results into a list so that we leave unread
946
894
    def get_multiple_ranges(self, file, file_size, ranges):
947
895
        self.send_response(206)
948
896
        self.send_header('Accept-Ranges', 'bytes')
 
897
        # XXX: this is strange; the 'random' name below seems undefined and
 
898
        # yet the tests pass -- mbp 2010-10-11 bug 658773
949
899
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
950
900
        self.send_header("Content-Type",
951
901
                         "multipart/byteranges; boundary=%s" % boundary)
1013
963
                return
1014
964
            self.send_range_content(file, start, end - start + 1)
1015
965
            cur += 1
1016
 
        # No final boundary
 
966
        # Final boundary
1017
967
        self.wfile.write(boundary_line)
1018
968
 
1019
969
 
1027
977
 
1028
978
    def test_readv_with_short_reads(self):
1029
979
        server = self.get_readonly_server()
1030
 
        t = self._transport(server.get_url())
 
980
        t = self.get_readonly_transport()
1031
981
        # Force separate ranges for each offset
1032
982
        t._bytes_to_read_before_seek = 0
1033
983
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
1048
998
        # that mode
1049
999
        self.assertEqual('single', t._range_hint)
1050
1000
 
 
1001
 
 
1002
class TruncatedBeforeBoundaryRequestHandler(
 
1003
    http_server.TestingHTTPRequestHandler):
 
1004
    """Truncation before a boundary, like in bug 198646"""
 
1005
 
 
1006
    _truncated_ranges = 1
 
1007
 
 
1008
    def get_multiple_ranges(self, file, file_size, ranges):
 
1009
        self.send_response(206)
 
1010
        self.send_header('Accept-Ranges', 'bytes')
 
1011
        boundary = 'tagada'
 
1012
        self.send_header('Content-Type',
 
1013
                         'multipart/byteranges; boundary=%s' % boundary)
 
1014
        boundary_line = '--%s\r\n' % boundary
 
1015
        # Calculate the Content-Length
 
1016
        content_length = 0
 
1017
        for (start, end) in ranges:
 
1018
            content_length += len(boundary_line)
 
1019
            content_length += self._header_line_length(
 
1020
                'Content-type', 'application/octet-stream')
 
1021
            content_length += self._header_line_length(
 
1022
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
 
1023
            content_length += len('\r\n') # end headers
 
1024
            content_length += end - start # + 1
 
1025
        content_length += len(boundary_line)
 
1026
        self.send_header('Content-length', content_length)
 
1027
        self.end_headers()
 
1028
 
 
1029
        # Send the multipart body
 
1030
        cur = 0
 
1031
        for (start, end) in ranges:
 
1032
            if cur + self._truncated_ranges >= len(ranges):
 
1033
                # Abruptly ends the response and close the connection
 
1034
                self.close_connection = 1
 
1035
                return
 
1036
            self.wfile.write(boundary_line)
 
1037
            self.send_header('Content-type', 'application/octet-stream')
 
1038
            self.send_header('Content-Range', 'bytes %d-%d/%d'
 
1039
                             % (start, end, file_size))
 
1040
            self.end_headers()
 
1041
            self.send_range_content(file, start, end - start + 1)
 
1042
            cur += 1
 
1043
        # Final boundary
 
1044
        self.wfile.write(boundary_line)
 
1045
 
 
1046
 
 
1047
class TestTruncatedBeforeBoundary(TestSpecificRequestHandler):
 
1048
    """Tests the case of bug 198646, disconnecting before a boundary."""
 
1049
 
 
1050
    _req_handler_class = TruncatedBeforeBoundaryRequestHandler
 
1051
 
 
1052
    def setUp(self):
 
1053
        super(TestTruncatedBeforeBoundary, self).setUp()
 
1054
        self.build_tree_contents([('a', '0123456789')],)
 
1055
 
 
1056
    def test_readv_with_short_reads(self):
 
1057
        server = self.get_readonly_server()
 
1058
        t = self.get_readonly_transport()
 
1059
        # Force separate ranges for each offset
 
1060
        t._bytes_to_read_before_seek = 0
 
1061
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
 
1062
        self.assertEqual((0, '0'), ireadv.next())
 
1063
        self.assertEqual((2, '2'), ireadv.next())
 
1064
        self.assertEqual((4, '45'), ireadv.next())
 
1065
        self.assertEqual((9, '9'), ireadv.next())
 
1066
 
 
1067
 
1051
1068
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1052
1069
    """Errors out when range specifiers exceed the limit"""
1053
1070
 
1077
1094
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1078
1095
    """Tests readv requests against a server erroring out on too much ranges."""
1079
1096
 
 
1097
    scenarios = multiply_scenarios(
 
1098
        vary_by_http_client_implementation(), 
 
1099
        vary_by_http_protocol_version(),
 
1100
        )
 
1101
 
1080
1102
    # Requests with more range specifiers will error out
1081
1103
    range_limit = 3
1082
1104
 
1084
1106
        return LimitedRangeHTTPServer(range_limit=self.range_limit,
1085
1107
                                      protocol_version=self._protocol_version)
1086
1108
 
1087
 
    def get_transport(self):
1088
 
        return self._transport(self.get_readonly_server().get_url())
1089
 
 
1090
1109
    def setUp(self):
1091
1110
        http_utils.TestCaseWithWebserver.setUp(self)
1092
1111
        # We need to manipulate ranges that correspond to real chunks in the
1096
1115
        self.build_tree_contents([('a', content)],)
1097
1116
 
1098
1117
    def test_few_ranges(self):
1099
 
        t = self.get_transport()
 
1118
        t = self.get_readonly_transport()
1100
1119
        l = list(t.readv('a', ((0, 4), (1024, 4), )))
1101
1120
        self.assertEqual(l[0], (0, '0000'))
1102
1121
        self.assertEqual(l[1], (1024, '0001'))
1103
1122
        self.assertEqual(1, self.get_readonly_server().GET_request_nb)
1104
1123
 
1105
1124
    def test_more_ranges(self):
1106
 
        t = self.get_transport()
 
1125
        t = self.get_readonly_transport()
1107
1126
        l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
1108
1127
        self.assertEqual(l[0], (0, '0000'))
1109
1128
        self.assertEqual(l[1], (1024, '0001'))
1120
1139
    Only the urllib implementation is tested here.
1121
1140
    """
1122
1141
 
1123
 
    def setUp(self):
1124
 
        tests.TestCase.setUp(self)
1125
 
        self._old_env = {}
1126
 
 
1127
 
    def tearDown(self):
1128
 
        self._restore_env()
1129
 
        tests.TestCase.tearDown(self)
1130
 
 
1131
 
    def _install_env(self, env):
1132
 
        for name, value in env.iteritems():
1133
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1134
 
 
1135
 
    def _restore_env(self):
1136
 
        for name, value in self._old_env.iteritems():
1137
 
            osutils.set_or_unset_env(name, value)
1138
 
 
1139
1142
    def _proxied_request(self):
1140
1143
        handler = _urllib2_wrappers.ProxyHandler()
1141
1144
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
1143
1146
        return request
1144
1147
 
1145
1148
    def test_empty_user(self):
1146
 
        self._install_env({'http_proxy': 'http://bar.com'})
 
1149
        self.overrideEnv('http_proxy', 'http://bar.com')
1147
1150
        request = self._proxied_request()
1148
1151
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
1149
1152
 
1150
1153
    def test_invalid_proxy(self):
1151
1154
        """A proxy env variable without scheme"""
1152
 
        self._install_env({'http_proxy': 'host:1234'})
 
1155
        self.overrideEnv('http_proxy', 'host:1234')
1153
1156
        self.assertRaises(errors.InvalidURL, self._proxied_request)
1154
1157
 
1155
1158
 
1162
1165
    to the file names).
1163
1166
    """
1164
1167
 
 
1168
    scenarios = multiply_scenarios(
 
1169
        vary_by_http_client_implementation(), 
 
1170
        vary_by_http_protocol_version(),
 
1171
        )
 
1172
 
1165
1173
    # FIXME: We don't have an https server available, so we don't
1166
 
    # test https connections.
 
1174
    # test https connections. --vila toolongago
1167
1175
 
1168
1176
    def setUp(self):
1169
1177
        super(TestProxyHttpServer, self).setUp()
 
1178
        self.transport_secondary_server = http_utils.ProxyServer
1170
1179
        self.build_tree_contents([('foo', 'contents of foo\n'),
1171
1180
                                  ('foo-proxied', 'proxied contents of foo\n')])
1172
1181
        # Let's setup some attributes for tests
1173
 
        self.server = self.get_readonly_server()
1174
 
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
 
1182
        server = self.get_readonly_server()
 
1183
        self.server_host_port = '%s:%d' % (server.host, server.port)
1175
1184
        if self._testing_pycurl():
1176
1185
            # Oh my ! pycurl does not check for the port as part of
1177
1186
            # no_proxy :-( So we just test the host part
1178
 
            self.no_proxy_host = 'localhost'
 
1187
            self.no_proxy_host = server.host
1179
1188
        else:
1180
 
            self.no_proxy_host = self.proxy_address
 
1189
            self.no_proxy_host = self.server_host_port
1181
1190
        # The secondary server is the proxy
1182
 
        self.proxy = self.get_secondary_server()
1183
 
        self.proxy_url = self.proxy.get_url()
1184
 
        self._old_env = {}
 
1191
        self.proxy_url = self.get_secondary_url()
1185
1192
 
1186
1193
    def _testing_pycurl(self):
1187
 
        return pycurl_present and self._transport == PyCurlTransport
1188
 
 
1189
 
    def create_transport_secondary_server(self):
1190
 
        """Creates an http server that will serve files with
1191
 
        '-proxied' appended to their names.
1192
 
        """
1193
 
        return http_utils.ProxyServer(protocol_version=self._protocol_version)
1194
 
 
1195
 
    def _install_env(self, env):
1196
 
        for name, value in env.iteritems():
1197
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1198
 
 
1199
 
    def _restore_env(self):
1200
 
        for name, value in self._old_env.iteritems():
1201
 
            osutils.set_or_unset_env(name, value)
1202
 
 
1203
 
    def proxied_in_env(self, env):
1204
 
        self._install_env(env)
1205
 
        url = self.server.get_url()
1206
 
        t = self._transport(url)
1207
 
        try:
1208
 
            self.assertEqual('proxied contents of foo\n', t.get('foo').read())
1209
 
        finally:
1210
 
            self._restore_env()
1211
 
 
1212
 
    def not_proxied_in_env(self, env):
1213
 
        self._install_env(env)
1214
 
        url = self.server.get_url()
1215
 
        t = self._transport(url)
1216
 
        try:
1217
 
            self.assertEqual('contents of foo\n', t.get('foo').read())
1218
 
        finally:
1219
 
            self._restore_env()
 
1194
        # TODO: This is duplicated for lots of the classes in this file
 
1195
        return (features.pycurl.available()
 
1196
                and self._transport == PyCurlTransport)
 
1197
 
 
1198
    def assertProxied(self):
 
1199
        t = self.get_readonly_transport()
 
1200
        self.assertEqual('proxied contents of foo\n', t.get('foo').read())
 
1201
 
 
1202
    def assertNotProxied(self):
 
1203
        t = self.get_readonly_transport()
 
1204
        self.assertEqual('contents of foo\n', t.get('foo').read())
1220
1205
 
1221
1206
    def test_http_proxy(self):
1222
 
        self.proxied_in_env({'http_proxy': self.proxy_url})
 
1207
        self.overrideEnv('http_proxy', self.proxy_url)
 
1208
        self.assertProxied()
1223
1209
 
1224
1210
    def test_HTTP_PROXY(self):
1225
1211
        if self._testing_pycurl():
1228
1214
            # about. Should we ?)
1229
1215
            raise tests.TestNotApplicable(
1230
1216
                'pycurl does not check HTTP_PROXY for security reasons')
1231
 
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
 
1217
        self.overrideEnv('HTTP_PROXY', self.proxy_url)
 
1218
        self.assertProxied()
1232
1219
 
1233
1220
    def test_all_proxy(self):
1234
 
        self.proxied_in_env({'all_proxy': self.proxy_url})
 
1221
        self.overrideEnv('all_proxy', self.proxy_url)
 
1222
        self.assertProxied()
1235
1223
 
1236
1224
    def test_ALL_PROXY(self):
1237
 
        self.proxied_in_env({'ALL_PROXY': self.proxy_url})
 
1225
        self.overrideEnv('ALL_PROXY', self.proxy_url)
 
1226
        self.assertProxied()
1238
1227
 
1239
1228
    def test_http_proxy_with_no_proxy(self):
1240
 
        self.not_proxied_in_env({'http_proxy': self.proxy_url,
1241
 
                                 'no_proxy': self.no_proxy_host})
 
1229
        self.overrideEnv('no_proxy', self.no_proxy_host)
 
1230
        self.overrideEnv('http_proxy', self.proxy_url)
 
1231
        self.assertNotProxied()
1242
1232
 
1243
1233
    def test_HTTP_PROXY_with_NO_PROXY(self):
1244
1234
        if self._testing_pycurl():
1245
1235
            raise tests.TestNotApplicable(
1246
1236
                'pycurl does not check HTTP_PROXY for security reasons')
1247
 
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
1248
 
                                 'NO_PROXY': self.no_proxy_host})
 
1237
        self.overrideEnv('NO_PROXY', self.no_proxy_host)
 
1238
        self.overrideEnv('HTTP_PROXY', self.proxy_url)
 
1239
        self.assertNotProxied()
1249
1240
 
1250
1241
    def test_all_proxy_with_no_proxy(self):
1251
 
        self.not_proxied_in_env({'all_proxy': self.proxy_url,
1252
 
                                 'no_proxy': self.no_proxy_host})
 
1242
        self.overrideEnv('no_proxy', self.no_proxy_host)
 
1243
        self.overrideEnv('all_proxy', self.proxy_url)
 
1244
        self.assertNotProxied()
1253
1245
 
1254
1246
    def test_ALL_PROXY_with_NO_PROXY(self):
1255
 
        self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
1256
 
                                 'NO_PROXY': self.no_proxy_host})
 
1247
        self.overrideEnv('NO_PROXY', self.no_proxy_host)
 
1248
        self.overrideEnv('ALL_PROXY', self.proxy_url)
 
1249
        self.assertNotProxied()
1257
1250
 
1258
1251
    def test_http_proxy_without_scheme(self):
 
1252
        self.overrideEnv('http_proxy', self.server_host_port)
1259
1253
        if self._testing_pycurl():
1260
1254
            # pycurl *ignores* invalid proxy env variables. If that ever change
1261
1255
            # in the future, this test will fail indicating that pycurl do not
1262
1256
            # ignore anymore such variables.
1263
 
            self.not_proxied_in_env({'http_proxy': self.proxy_address})
 
1257
            self.assertNotProxied()
1264
1258
        else:
1265
 
            self.assertRaises(errors.InvalidURL,
1266
 
                              self.proxied_in_env,
1267
 
                              {'http_proxy': self.proxy_address})
 
1259
            self.assertRaises(errors.InvalidURL, self.assertProxied)
1268
1260
 
1269
1261
 
1270
1262
class TestRanges(http_utils.TestCaseWithWebserver):
1271
1263
    """Test the Range header in GET methods."""
1272
1264
 
 
1265
    scenarios = multiply_scenarios(
 
1266
        vary_by_http_client_implementation(), 
 
1267
        vary_by_http_protocol_version(),
 
1268
        )
 
1269
 
1273
1270
    def setUp(self):
1274
1271
        http_utils.TestCaseWithWebserver.setUp(self)
1275
1272
        self.build_tree_contents([('a', '0123456789')],)
1276
 
        server = self.get_readonly_server()
1277
 
        self.transport = self._transport(server.get_url())
1278
1273
 
1279
1274
    def create_transport_readonly_server(self):
1280
1275
        return http_server.HttpServer(protocol_version=self._protocol_version)
1281
1276
 
1282
1277
    def _file_contents(self, relpath, ranges):
 
1278
        t = self.get_readonly_transport()
1283
1279
        offsets = [ (start, end - start + 1) for start, end in ranges]
1284
 
        coalesce = self.transport._coalesce_offsets
 
1280
        coalesce = t._coalesce_offsets
1285
1281
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
1286
 
        code, data = self.transport._get(relpath, coalesced)
 
1282
        code, data = t._get(relpath, coalesced)
1287
1283
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1288
1284
        for start, end in ranges:
1289
1285
            data.seek(start)
1290
1286
            yield data.read(end - start + 1)
1291
1287
 
1292
1288
    def _file_tail(self, relpath, tail_amount):
1293
 
        code, data = self.transport._get(relpath, [], tail_amount)
 
1289
        t = self.get_readonly_transport()
 
1290
        code, data = t._get(relpath, [], tail_amount)
1294
1291
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1295
1292
        data.seek(-tail_amount, 2)
1296
1293
        return data.read(tail_amount)
1315
1312
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1316
1313
    """Test redirection between http servers."""
1317
1314
 
1318
 
    def create_transport_secondary_server(self):
1319
 
        """Create the secondary server redirecting to the primary server"""
1320
 
        new = self.get_readonly_server()
1321
 
 
1322
 
        redirecting = http_utils.HTTPServerRedirecting(
1323
 
            protocol_version=self._protocol_version)
1324
 
        redirecting.redirect_to(new.host, new.port)
1325
 
        return redirecting
 
1315
    scenarios = multiply_scenarios(
 
1316
        vary_by_http_client_implementation(), 
 
1317
        vary_by_http_protocol_version(),
 
1318
        )
1326
1319
 
1327
1320
    def setUp(self):
1328
1321
        super(TestHTTPRedirections, self).setUp()
1330
1323
                                  ('bundle',
1331
1324
                                  '# Bazaar revision bundle v0.9\n#\n')
1332
1325
                                  ],)
1333
 
        # The requests to the old server will be redirected to the new server
1334
 
        self.old_transport = self._transport(self.old_server.get_url())
1335
1326
 
1336
1327
    def test_redirected(self):
1337
 
        self.assertRaises(errors.RedirectRequested, self.old_transport.get, 'a')
1338
 
        t = self._transport(self.new_server.get_url())
1339
 
        self.assertEqual('0123456789', t.get('a').read())
1340
 
 
1341
 
    def test_read_redirected_bundle_from_url(self):
1342
 
        from bzrlib.bundle import read_bundle_from_url
1343
 
        url = self.old_transport.abspath('bundle')
1344
 
        bundle = self.applyDeprecated(deprecated_in((1, 12, 0)),
1345
 
                read_bundle_from_url, url)
1346
 
        # If read_bundle_from_url was successful we get an empty bundle
1347
 
        self.assertEqual([], bundle.revisions)
 
1328
        self.assertRaises(errors.RedirectRequested,
 
1329
                          self.get_old_transport().get, 'a')
 
1330
        self.assertEqual('0123456789', self.get_new_transport().get('a').read())
1348
1331
 
1349
1332
 
1350
1333
class RedirectedRequest(_urllib2_wrappers.Request):
1363
1346
        self.follow_redirections = True
1364
1347
 
1365
1348
 
 
1349
def install_redirected_request(test):
 
1350
    test.overrideAttr(_urllib2_wrappers, 'Request', RedirectedRequest)
 
1351
 
 
1352
 
 
1353
def cleanup_http_redirection_connections(test):
 
1354
    # Some sockets are opened but never seen by _urllib, so we trap them at
 
1355
    # the _urllib2_wrappers level to be able to clean them up.
 
1356
    def socket_disconnect(sock):
 
1357
        try:
 
1358
            sock.shutdown(socket.SHUT_RDWR)
 
1359
            sock.close()
 
1360
        except socket.error:
 
1361
            pass
 
1362
    def connect(connection):
 
1363
        test.http_connect_orig(connection)
 
1364
        test.addCleanup(socket_disconnect, connection.sock)
 
1365
    test.http_connect_orig = test.overrideAttr(
 
1366
        _urllib2_wrappers.HTTPConnection, 'connect', connect)
 
1367
    def connect(connection):
 
1368
        test.https_connect_orig(connection)
 
1369
        test.addCleanup(socket_disconnect, connection.sock)
 
1370
    test.https_connect_orig = test.overrideAttr(
 
1371
        _urllib2_wrappers.HTTPSConnection, 'connect', connect)
 
1372
 
 
1373
 
1366
1374
class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):
1367
1375
    """Test redirections.
1368
1376
 
1377
1385
    -- vila 20070212
1378
1386
    """
1379
1387
 
 
1388
    scenarios = multiply_scenarios(
 
1389
        vary_by_http_client_implementation(), 
 
1390
        vary_by_http_protocol_version(),
 
1391
        )
 
1392
 
1380
1393
    def setUp(self):
1381
 
        if pycurl_present and self._transport == PyCurlTransport:
 
1394
        if (features.pycurl.available()
 
1395
            and self._transport == PyCurlTransport):
1382
1396
            raise tests.TestNotApplicable(
1383
 
                "pycurl doesn't redirect silently annymore")
 
1397
                "pycurl doesn't redirect silently anymore")
1384
1398
        super(TestHTTPSilentRedirections, self).setUp()
1385
 
        self.setup_redirected_request()
1386
 
        self.addCleanup(self.cleanup_redirected_request)
 
1399
        install_redirected_request(self)
 
1400
        cleanup_http_redirection_connections(self)
1387
1401
        self.build_tree_contents([('a','a'),
1388
1402
                                  ('1/',),
1389
1403
                                  ('1/a', 'redirected once'),
1397
1411
                                  ('5/a', 'redirected 5 times'),
1398
1412
                                  ],)
1399
1413
 
1400
 
        self.old_transport = self._transport(self.old_server.get_url())
1401
 
 
1402
 
    def setup_redirected_request(self):
1403
 
        self.original_class = _urllib2_wrappers.Request
1404
 
        _urllib2_wrappers.Request = RedirectedRequest
1405
 
 
1406
 
    def cleanup_redirected_request(self):
1407
 
        _urllib2_wrappers.Request = self.original_class
1408
 
 
1409
 
    def create_transport_secondary_server(self):
1410
 
        """Create the secondary server, redirections are defined in the tests"""
1411
 
        return http_utils.HTTPServerRedirecting(
1412
 
            protocol_version=self._protocol_version)
1413
 
 
1414
1414
    def test_one_redirection(self):
1415
 
        t = self.old_transport
1416
 
 
1417
 
        req = RedirectedRequest('GET', t.abspath('a'))
1418
 
        req.follow_redirections = True
 
1415
        t = self.get_old_transport()
 
1416
        req = RedirectedRequest('GET', t._remote_path('a'))
1419
1417
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1420
1418
                                       self.new_server.port)
1421
1419
        self.old_server.redirections = \
1422
1420
            [('(.*)', r'%s/1\1' % (new_prefix), 301),]
1423
 
        self.assertEquals('redirected once',t._perform(req).read())
 
1421
        self.assertEqual('redirected once', t._perform(req).read())
1424
1422
 
1425
1423
    def test_five_redirections(self):
1426
 
        t = self.old_transport
1427
 
 
1428
 
        req = RedirectedRequest('GET', t.abspath('a'))
1429
 
        req.follow_redirections = True
 
1424
        t = self.get_old_transport()
 
1425
        req = RedirectedRequest('GET', t._remote_path('a'))
1430
1426
        old_prefix = 'http://%s:%s' % (self.old_server.host,
1431
1427
                                       self.old_server.port)
1432
1428
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1438
1434
            ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1439
1435
            ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1440
1436
            ]
1441
 
        self.assertEquals('redirected 5 times',t._perform(req).read())
 
1437
        self.assertEqual('redirected 5 times', t._perform(req).read())
1442
1438
 
1443
1439
 
1444
1440
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
1445
1441
    """Test transport.do_catching_redirections."""
1446
1442
 
 
1443
    scenarios = multiply_scenarios(
 
1444
        vary_by_http_client_implementation(), 
 
1445
        vary_by_http_protocol_version(),
 
1446
        )
 
1447
 
1447
1448
    def setUp(self):
1448
1449
        super(TestDoCatchRedirections, self).setUp()
1449
1450
        self.build_tree_contents([('a', '0123456789'),],)
1450
 
 
1451
 
        self.old_transport = self._transport(self.old_server.get_url())
1452
 
 
1453
 
    def get_a(self, transport):
1454
 
        return transport.get('a')
 
1451
        cleanup_http_redirection_connections(self)
 
1452
 
 
1453
        self.old_transport = self.get_old_transport()
 
1454
 
 
1455
    def get_a(self, t):
 
1456
        return t.get('a')
1455
1457
 
1456
1458
    def test_no_redirection(self):
1457
 
        t = self._transport(self.new_server.get_url())
 
1459
        t = self.get_new_transport()
1458
1460
 
1459
1461
        # We use None for redirected so that we fail if redirected
1460
 
        self.assertEquals('0123456789',
1461
 
                          transport.do_catching_redirections(
 
1462
        self.assertEqual('0123456789',
 
1463
                         transport.do_catching_redirections(
1462
1464
                self.get_a, t, None).read())
1463
1465
 
1464
1466
    def test_one_redirection(self):
1465
1467
        self.redirections = 0
1466
1468
 
1467
 
        def redirected(transport, exception, redirection_notice):
 
1469
        def redirected(t, exception, redirection_notice):
1468
1470
            self.redirections += 1
1469
 
            dir, file = urlutils.split(exception.target)
1470
 
            return self._transport(dir)
 
1471
            redirected_t = t._redirected_to(exception.source, exception.target)
 
1472
            return redirected_t
1471
1473
 
1472
 
        self.assertEquals('0123456789',
1473
 
                          transport.do_catching_redirections(
 
1474
        self.assertEqual('0123456789',
 
1475
                         transport.do_catching_redirections(
1474
1476
                self.get_a, self.old_transport, redirected).read())
1475
 
        self.assertEquals(1, self.redirections)
 
1477
        self.assertEqual(1, self.redirections)
1476
1478
 
1477
1479
    def test_redirection_loop(self):
1478
1480
 
1490
1492
class TestAuth(http_utils.TestCaseWithWebserver):
1491
1493
    """Test authentication scheme"""
1492
1494
 
 
1495
    scenarios = multiply_scenarios(
 
1496
        vary_by_http_client_implementation(),
 
1497
        vary_by_http_protocol_version(),
 
1498
        vary_by_http_auth_scheme(),
 
1499
        )
 
1500
 
1493
1501
    _auth_header = 'Authorization'
1494
1502
    _password_prompt_prefix = ''
1495
1503
    _username_prompt_prefix = ''
1503
1511
                                  ('b', 'contents of b\n'),])
1504
1512
 
1505
1513
    def create_transport_readonly_server(self):
1506
 
        return self._auth_server(protocol_version=self._protocol_version)
 
1514
        server = self._auth_server(protocol_version=self._protocol_version)
 
1515
        server._url_protocol = self._url_protocol
 
1516
        return server
1507
1517
 
1508
1518
    def _testing_pycurl(self):
1509
 
        return pycurl_present and self._transport == PyCurlTransport
 
1519
        # TODO: This is duplicated for lots of the classes in this file
 
1520
        return (features.pycurl.available()
 
1521
                and self._transport == PyCurlTransport)
1510
1522
 
1511
1523
    def get_user_url(self, user, password):
1512
1524
        """Build an url embedding user and password"""
1520
1532
        return url
1521
1533
 
1522
1534
    def get_user_transport(self, user, password):
1523
 
        return self._transport(self.get_user_url(user, password))
 
1535
        t = transport.get_transport(self.get_user_url(user, password))
 
1536
        return t
1524
1537
 
1525
1538
    def test_no_user(self):
1526
1539
        self.server.add_user('joe', 'foo')
1577
1590
        self.assertEqual('', ui.ui_factory.stdin.readline())
1578
1591
        stderr.seek(0)
1579
1592
        expected_prompt = self._expected_username_prompt(t._unqualified_scheme)
1580
 
        self.assertEquals(expected_prompt, stderr.read(len(expected_prompt)))
1581
 
        self.assertEquals('', stdout.getvalue())
 
1593
        self.assertEqual(expected_prompt, stderr.read(len(expected_prompt)))
 
1594
        self.assertEqual('', stdout.getvalue())
1582
1595
        self._check_password_prompt(t._unqualified_scheme, 'joe',
1583
1596
                                    stderr.readline())
1584
1597
 
1599
1612
        self.assertEqual('', ui.ui_factory.stdin.readline())
1600
1613
        self._check_password_prompt(t._unqualified_scheme, 'joe',
1601
1614
                                    stderr.getvalue())
1602
 
        self.assertEquals('', stdout.getvalue())
 
1615
        self.assertEqual('', stdout.getvalue())
1603
1616
        # And we shouldn't prompt again for a different request
1604
1617
        # against the same transport.
1605
1618
        self.assertEqual('contents of b\n',t.get('b').read())
1615
1628
                              % (scheme.upper(),
1616
1629
                                 user, self.server.host, self.server.port,
1617
1630
                                 self.server.auth_realm)))
1618
 
        self.assertEquals(expected_prompt, actual_prompt)
 
1631
        self.assertEqual(expected_prompt, actual_prompt)
1619
1632
 
1620
1633
    def _expected_username_prompt(self, scheme):
1621
1634
        return (self._username_prompt_prefix
1635
1648
        self.server.add_user(user, password)
1636
1649
        t = self.get_user_transport(user, None)
1637
1650
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1638
 
                                            stdout=tests.StringIOWrapper())
 
1651
                                            stderr=tests.StringIOWrapper())
1639
1652
        # Create a minimal config file with the right password
1640
 
        conf = config.AuthenticationConfig()
1641
 
        conf._get_config().update(
1642
 
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1643
 
                          'user': user, 'password': password}})
1644
 
        conf._save()
 
1653
        _setup_authentication_config(
 
1654
            scheme='http', 
 
1655
            port=self.server.port,
 
1656
            user=user,
 
1657
            password=password)
1645
1658
        # Issue a request to the server to connect
1646
1659
        self.assertEqual('contents of a\n',t.get('a').read())
1647
1660
        # stdin should have  been left untouched
1649
1662
        # Only one 'Authentication Required' error should occur
1650
1663
        self.assertEqual(1, self.server.auth_required_errors)
1651
1664
 
1652
 
    def test_user_from_auth_conf(self):
1653
 
        if self._testing_pycurl():
1654
 
            raise tests.TestNotApplicable(
1655
 
                'pycurl does not support authentication.conf')
1656
 
        user = 'joe'
1657
 
        password = 'foo'
1658
 
        self.server.add_user(user, password)
1659
 
        # Create a minimal config file with the right password
1660
 
        conf = config.AuthenticationConfig()
1661
 
        conf._get_config().update(
1662
 
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1663
 
                          'user': user, 'password': password}})
1664
 
        conf._save()
1665
 
        t = self.get_user_transport(None, None)
1666
 
        # Issue a request to the server to connect
1667
 
        self.assertEqual('contents of a\n', t.get('a').read())
1668
 
        # Only one 'Authentication Required' error should occur
1669
 
        self.assertEqual(1, self.server.auth_required_errors)
1670
 
 
1671
1665
    def test_changing_nonce(self):
1672
1666
        if self._auth_server not in (http_utils.HTTPDigestAuthServer,
1673
1667
                                     http_utils.ProxyDigestAuthServer):
1689
1683
        # initial 'who are you' and a second 'who are you' with the new nonce)
1690
1684
        self.assertEqual(2, self.server.auth_required_errors)
1691
1685
 
 
1686
    def test_user_from_auth_conf(self):
 
1687
        if self._testing_pycurl():
 
1688
            raise tests.TestNotApplicable(
 
1689
                'pycurl does not support authentication.conf')
 
1690
        user = 'joe'
 
1691
        password = 'foo'
 
1692
        self.server.add_user(user, password)
 
1693
        _setup_authentication_config(
 
1694
            scheme='http', 
 
1695
            port=self.server.port,
 
1696
            user=user,
 
1697
            password=password)
 
1698
        t = self.get_user_transport(None, None)
 
1699
        # Issue a request to the server to connect
 
1700
        self.assertEqual('contents of a\n', t.get('a').read())
 
1701
        # Only one 'Authentication Required' error should occur
 
1702
        self.assertEqual(1, self.server.auth_required_errors)
 
1703
 
 
1704
 
 
1705
def _setup_authentication_config(**kwargs):
 
1706
    conf = config.AuthenticationConfig()
 
1707
    conf._get_config().update({'httptest': kwargs})
 
1708
    conf._save()
 
1709
 
 
1710
 
 
1711
 
 
1712
class TestUrllib2AuthHandler(tests.TestCaseWithTransport):
 
1713
    """Unit tests for glue by which urllib2 asks us for authentication"""
 
1714
 
 
1715
    def test_get_user_password_without_port(self):
 
1716
        """We cope if urllib2 doesn't tell us the port.
 
1717
 
 
1718
        See https://bugs.launchpad.net/bzr/+bug/654684
 
1719
        """
 
1720
        user = 'joe'
 
1721
        password = 'foo'
 
1722
        _setup_authentication_config(
 
1723
            scheme='http', 
 
1724
            host='localhost',
 
1725
            user=user,
 
1726
            password=password)
 
1727
        handler = _urllib2_wrappers.HTTPAuthHandler()
 
1728
        got_pass = handler.get_user_password(dict(
 
1729
            user='joe',
 
1730
            protocol='http',
 
1731
            host='localhost',
 
1732
            path='/',
 
1733
            realm='Realm',
 
1734
            ))
 
1735
        self.assertEquals((user, password), got_pass)
1692
1736
 
1693
1737
 
1694
1738
class TestProxyAuth(TestAuth):
1695
1739
    """Test proxy authentication schemes."""
1696
1740
 
 
1741
    scenarios = multiply_scenarios(
 
1742
        vary_by_http_client_implementation(),
 
1743
        vary_by_http_protocol_version(),
 
1744
        vary_by_http_proxy_auth_scheme(),
 
1745
        )
 
1746
 
1697
1747
    _auth_header = 'Proxy-authorization'
1698
1748
    _password_prompt_prefix = 'Proxy '
1699
1749
    _username_prompt_prefix = 'Proxy '
1700
1750
 
1701
1751
    def setUp(self):
1702
1752
        super(TestProxyAuth, self).setUp()
1703
 
        self._old_env = {}
1704
 
        self.addCleanup(self._restore_env)
1705
1753
        # Override the contents to avoid false positives
1706
1754
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
1707
1755
                                  ('b', 'not proxied contents of b\n'),
1710
1758
                                  ])
1711
1759
 
1712
1760
    def get_user_transport(self, user, password):
1713
 
        self._install_env({'all_proxy': self.get_user_url(user, password)})
1714
 
        return self._transport(self.server.get_url())
1715
 
 
1716
 
    def _install_env(self, env):
1717
 
        for name, value in env.iteritems():
1718
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1719
 
 
1720
 
    def _restore_env(self):
1721
 
        for name, value in self._old_env.iteritems():
1722
 
            osutils.set_or_unset_env(name, value)
 
1761
        self.overrideEnv('all_proxy', self.get_user_url(user, password))
 
1762
        return TestAuth.get_user_transport(self, user, password)
1723
1763
 
1724
1764
    def test_empty_pass(self):
1725
1765
        if self._testing_pycurl():
1744
1784
        self.readfile = StringIO(socket_read_content)
1745
1785
        self.writefile = StringIO()
1746
1786
        self.writefile.close = lambda: None
 
1787
        self.close = lambda: None
1747
1788
 
1748
1789
    def makefile(self, mode='r', bufsize=None):
1749
1790
        if 'r' in mode:
1754
1795
 
1755
1796
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1756
1797
 
 
1798
    scenarios = multiply_scenarios(
 
1799
        vary_by_http_client_implementation(), 
 
1800
        vary_by_http_protocol_version(),
 
1801
        )
 
1802
 
1757
1803
    def setUp(self):
1758
1804
        super(SmartHTTPTunnellingTest, self).setUp()
1759
1805
        # We use the VFS layer as part of HTTP tunnelling tests.
1760
 
        self._captureVar('BZR_NO_SMART_VFS', None)
 
1806
        self.overrideEnv('BZR_NO_SMART_VFS', None)
1761
1807
        self.transport_readonly_server = http_utils.HTTPServerWithSmarts
 
1808
        self.http_server = self.get_readonly_server()
1762
1809
 
1763
1810
    def create_transport_readonly_server(self):
1764
 
        return http_utils.HTTPServerWithSmarts(
 
1811
        server = http_utils.HTTPServerWithSmarts(
1765
1812
            protocol_version=self._protocol_version)
 
1813
        server._url_protocol = self._url_protocol
 
1814
        return server
1766
1815
 
1767
1816
    def test_open_bzrdir(self):
1768
1817
        branch = self.make_branch('relpath')
1769
 
        http_server = self.get_readonly_server()
1770
 
        url = http_server.get_url() + 'relpath'
 
1818
        url = self.http_server.get_url() + 'relpath'
1771
1819
        bd = bzrdir.BzrDir.open(url)
 
1820
        self.addCleanup(bd.transport.disconnect)
1772
1821
        self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1773
1822
 
1774
1823
    def test_bulk_data(self):
1776
1825
        # The 'readv' command in the smart protocol both sends and receives
1777
1826
        # bulk data, so we use that.
1778
1827
        self.build_tree(['data-file'])
1779
 
        http_server = self.get_readonly_server()
1780
 
        http_transport = self._transport(http_server.get_url())
 
1828
        http_transport = transport.get_transport(self.http_server.get_url())
1781
1829
        medium = http_transport.get_smart_medium()
1782
1830
        # Since we provide the medium, the url below will be mostly ignored
1783
1831
        # during the test, as long as the path is '/'.
1791
1839
        post_body = 'hello\n'
1792
1840
        expected_reply_body = 'ok\x012\n'
1793
1841
 
1794
 
        http_server = self.get_readonly_server()
1795
 
        http_transport = self._transport(http_server.get_url())
 
1842
        http_transport = transport.get_transport(self.http_server.get_url())
1796
1843
        medium = http_transport.get_smart_medium()
1797
1844
        response = medium.send_http_smart_request(post_body)
1798
1845
        reply_body = response.read()
1799
1846
        self.assertEqual(expected_reply_body, reply_body)
1800
1847
 
1801
1848
    def test_smart_http_server_post_request_handler(self):
1802
 
        httpd = self.get_readonly_server()._get_httpd()
 
1849
        httpd = self.http_server.server
1803
1850
 
1804
1851
        socket = SampleSocket(
1805
1852
            'POST /.bzr/smart %s \r\n' % self._protocol_version
1837
1884
 
1838
1885
    def test_probe_smart_server(self):
1839
1886
        """Test error handling against server refusing smart requests."""
1840
 
        server = self.get_readonly_server()
1841
 
        t = self._transport(server.get_url())
 
1887
        t = self.get_readonly_transport()
1842
1888
        # No need to build a valid smart request here, the server will not even
1843
1889
        # try to interpret it.
1844
1890
        self.assertRaises(errors.SmartProtocolError,
1845
1891
                          t.get_smart_medium().send_http_smart_request,
1846
1892
                          'whatever')
1847
1893
 
 
1894
 
1848
1895
class Test_redirected_to(tests.TestCase):
1849
1896
 
 
1897
    scenarios = vary_by_http_client_implementation()
 
1898
 
1850
1899
    def test_redirected_to_subdir(self):
1851
1900
        t = self._transport('http://www.example.com/foo')
1852
1901
        r = t._redirected_to('http://www.example.com/foo',
1853
1902
                             'http://www.example.com/foo/subdir')
1854
1903
        self.assertIsInstance(r, type(t))
1855
1904
        # Both transports share the some connection
1856
 
        self.assertEquals(t._get_connection(), r._get_connection())
 
1905
        self.assertEqual(t._get_connection(), r._get_connection())
1857
1906
 
1858
1907
    def test_redirected_to_self_with_slash(self):
1859
1908
        t = self._transport('http://www.example.com/foo')
1863
1912
        # Both transports share the some connection (one can argue that we
1864
1913
        # should return the exact same transport here, but that seems
1865
1914
        # overkill).
1866
 
        self.assertEquals(t._get_connection(), r._get_connection())
 
1915
        self.assertEqual(t._get_connection(), r._get_connection())
1867
1916
 
1868
1917
    def test_redirected_to_host(self):
1869
1918
        t = self._transport('http://www.example.com/foo')
1888
1937
        r = t._redirected_to('http://www.example.com/foo',
1889
1938
                             'https://foo.example.com/foo')
1890
1939
        self.assertIsInstance(r, type(t))
1891
 
        self.assertEquals(t._user, r._user)
 
1940
        self.assertEqual(t._user, r._user)
1892
1941
 
1893
1942
 
1894
1943
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1903
1952
    line.
1904
1953
    """
1905
1954
 
1906
 
    def handle_one_request(self):
 
1955
    def _handle_one_request(self):
1907
1956
        tcs = self.server.test_case_server
1908
1957
        requestline = self.rfile.readline()
1909
1958
        headers = self.MessageClass(self.rfile, 0)
1953
2002
        pass
1954
2003
 
1955
2004
 
1956
 
class TestActivity(tests.TestCase):
 
2005
class TestActivityMixin(object):
1957
2006
    """Test socket activity reporting.
1958
2007
 
1959
2008
    We use a special purpose server to control the bytes sent and received and
1963
2012
    def setUp(self):
1964
2013
        tests.TestCase.setUp(self)
1965
2014
        self.server = self._activity_server(self._protocol_version)
1966
 
        self.server.setUp()
 
2015
        self.server.start_server()
1967
2016
        self.activities = {}
1968
2017
        def report_activity(t, bytes, direction):
1969
2018
            count = self.activities.get(direction, 0)
1973
2022
        # We override at class level because constructors may propagate the
1974
2023
        # bound method and render instance overriding ineffective (an
1975
2024
        # alternative would be to define a specific ui factory instead...)
1976
 
        self.orig_report_activity = self._transport._report_activity
1977
 
        self._transport._report_activity = report_activity
1978
 
 
1979
 
    def tearDown(self):
1980
 
        self._transport._report_activity = self.orig_report_activity
1981
 
        self.server.tearDown()
1982
 
        tests.TestCase.tearDown(self)
 
2025
        self.overrideAttr(self._transport, '_report_activity', report_activity)
 
2026
        self.addCleanup(self.server.stop_server)
1983
2027
 
1984
2028
    def get_transport(self):
1985
 
        return self._transport(self.server.get_url())
 
2029
        t = self._transport(self.server.get_url())
 
2030
        # FIXME: Needs cleanup -- vila 20100611
 
2031
        return t
1986
2032
 
1987
2033
    def assertActivitiesMatch(self):
1988
2034
        self.assertEqual(self.server.bytes_read,
2093
2139
'''
2094
2140
        t = self.get_transport()
2095
2141
        # We must send a single line of body bytes, see
2096
 
        # PredefinedRequestHandler.handle_one_request
 
2142
        # PredefinedRequestHandler._handle_one_request
2097
2143
        code, f = t._post('abc def end-of-body\n')
2098
2144
        self.assertEqual('lalala whatever as long as itsssss\n', f.read())
2099
2145
        self.assertActivitiesMatch()
 
2146
 
 
2147
 
 
2148
class TestActivity(tests.TestCase, TestActivityMixin):
 
2149
 
 
2150
    scenarios = multiply_scenarios(
 
2151
        vary_by_http_activity(),
 
2152
        vary_by_http_protocol_version(),
 
2153
        )
 
2154
 
 
2155
    def setUp(self):
 
2156
        TestActivityMixin.setUp(self)
 
2157
 
 
2158
 
 
2159
class TestNoReportActivity(tests.TestCase, TestActivityMixin):
 
2160
 
 
2161
    # Unlike TestActivity, we are really testing ReportingFileSocket and
 
2162
    # ReportingSocket, so we don't need all the parametrization. Since
 
2163
    # ReportingFileSocket and ReportingSocket are wrappers, it's easier to
 
2164
    # test them through their use by the transport than directly (that's a
 
2165
    # bit less clean but far more simpler and effective).
 
2166
    _activity_server = ActivityHTTPServer
 
2167
    _protocol_version = 'HTTP/1.1'
 
2168
 
 
2169
    def setUp(self):
 
2170
        self._transport =_urllib.HttpTransport_urllib
 
2171
        TestActivityMixin.setUp(self)
 
2172
 
 
2173
    def assertActivitiesMatch(self):
 
2174
        # Nothing to check here
 
2175
        pass
 
2176
 
 
2177
 
 
2178
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
 
2179
    """Test authentication on the redirected http server."""
 
2180
 
 
2181
    scenarios = vary_by_http_protocol_version()
 
2182
 
 
2183
    _auth_header = 'Authorization'
 
2184
    _password_prompt_prefix = ''
 
2185
    _username_prompt_prefix = ''
 
2186
    _auth_server = http_utils.HTTPBasicAuthServer
 
2187
    _transport = _urllib.HttpTransport_urllib
 
2188
 
 
2189
    def setUp(self):
 
2190
        super(TestAuthOnRedirected, self).setUp()
 
2191
        self.build_tree_contents([('a','a'),
 
2192
                                  ('1/',),
 
2193
                                  ('1/a', 'redirected once'),
 
2194
                                  ],)
 
2195
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
2196
                                       self.new_server.port)
 
2197
        self.old_server.redirections = [
 
2198
            ('(.*)', r'%s/1\1' % (new_prefix), 301),]
 
2199
        self.old_transport = self.get_old_transport()
 
2200
        self.new_server.add_user('joe', 'foo')
 
2201
        cleanup_http_redirection_connections(self)
 
2202
 
 
2203
    def create_transport_readonly_server(self):
 
2204
        server = self._auth_server(protocol_version=self._protocol_version)
 
2205
        server._url_protocol = self._url_protocol
 
2206
        return server
 
2207
 
 
2208
    def get_a(self, t):
 
2209
        return t.get('a')
 
2210
 
 
2211
    def test_auth_on_redirected_via_do_catching_redirections(self):
 
2212
        self.redirections = 0
 
2213
 
 
2214
        def redirected(t, exception, redirection_notice):
 
2215
            self.redirections += 1
 
2216
            redirected_t = t._redirected_to(exception.source, exception.target)
 
2217
            self.addCleanup(redirected_t.disconnect)
 
2218
            return redirected_t
 
2219
 
 
2220
        stdout = tests.StringIOWrapper()
 
2221
        stderr = tests.StringIOWrapper()
 
2222
        ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
 
2223
                                            stdout=stdout, stderr=stderr)
 
2224
        self.assertEqual('redirected once',
 
2225
                         transport.do_catching_redirections(
 
2226
                self.get_a, self.old_transport, redirected).read())
 
2227
        self.assertEqual(1, self.redirections)
 
2228
        # stdin should be empty
 
2229
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
2230
        # stdout should be empty, stderr will contains the prompts
 
2231
        self.assertEqual('', stdout.getvalue())
 
2232
 
 
2233
    def test_auth_on_redirected_via_following_redirections(self):
 
2234
        self.new_server.add_user('joe', 'foo')
 
2235
        stdout = tests.StringIOWrapper()
 
2236
        stderr = tests.StringIOWrapper()
 
2237
        ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
 
2238
                                            stdout=stdout, stderr=stderr)
 
2239
        t = self.old_transport
 
2240
        req = RedirectedRequest('GET', t.abspath('a'))
 
2241
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
2242
                                       self.new_server.port)
 
2243
        self.old_server.redirections = [
 
2244
            ('(.*)', r'%s/1\1' % (new_prefix), 301),]
 
2245
        self.assertEqual('redirected once', t._perform(req).read())
 
2246
        # stdin should be empty
 
2247
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
2248
        # stdout should be empty, stderr will contains the prompts
 
2249
        self.assertEqual('', stdout.getvalue())
 
2250
 
 
2251