~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-08-17 18:13:57 UTC
  • mfrom: (5268.7.29 transport-segments)
  • Revision ID: pqm@pqm.ubuntu.com-20110817181357-y5q5eth1hk8bl3om
(jelmer) Allow specifying the colocated branch to use in the branch URL,
 and retrieving the branch name using ControlDir._get_selected_branch.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Tests for HTTP implementations.
18
18
 
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
35
32
import bzrlib
36
33
from bzrlib import (
37
34
    bzrdir,
 
35
    cethread,
38
36
    config,
 
37
    debug,
39
38
    errors,
40
39
    osutils,
41
40
    remote as _mod_remote,
42
41
    tests,
 
42
    trace,
43
43
    transport,
44
44
    ui,
45
 
    urlutils,
46
45
    )
47
46
from bzrlib.tests import (
 
47
    features,
48
48
    http_server,
49
49
    http_utils,
 
50
    test_server,
 
51
    )
 
52
from bzrlib.tests.scenarios import (
 
53
    load_tests_apply_scenarios,
 
54
    multiply_scenarios,
50
55
    )
51
56
from bzrlib.transport import (
52
57
    http,
58
63
    )
59
64
 
60
65
 
61
 
try:
 
66
if features.pycurl.available():
62
67
    from bzrlib.transport.http._pycurl import PyCurlTransport
63
 
    pycurl_present = True
64
 
except errors.DependencyNotPresent:
65
 
    pycurl_present = False
66
 
 
67
 
 
68
 
class TransportAdapter(tests.TestScenarioApplier):
69
 
    """Generate the same test for each transport implementation."""
70
 
 
71
 
    def __init__(self):
72
 
        transport_scenarios = [
73
 
            ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
74
 
                            _server=http_server.HttpServer_urllib,
75
 
                            _qualified_prefix='http+urllib',)),
76
 
            ]
77
 
        if pycurl_present:
78
 
            transport_scenarios.append(
79
 
                ('pycurl', dict(_transport=PyCurlTransport,
80
 
                                _server=http_server.HttpServer_PyCurl,
81
 
                                _qualified_prefix='http+pycurl',)))
82
 
        self.scenarios = transport_scenarios
83
 
 
84
 
 
85
 
class TransportProtocolAdapter(TransportAdapter):
86
 
    """Generate the same test for each protocol implementation.
87
 
 
88
 
    In addition to the transport adaptatation that we inherit from.
89
 
    """
90
 
 
91
 
    def __init__(self):
92
 
        super(TransportProtocolAdapter, self).__init__()
93
 
        protocol_scenarios = [
94
 
            ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
95
 
            ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
96
 
            ]
97
 
        self.scenarios = tests.multiply_scenarios(self.scenarios,
98
 
                                                  protocol_scenarios)
99
 
 
100
 
 
101
 
class TransportProtocolAuthenticationAdapter(TransportProtocolAdapter):
102
 
    """Generate the same test for each authentication scheme implementation.
103
 
 
104
 
    In addition to the protocol adaptatation that we inherit from.
105
 
    """
106
 
 
107
 
    def __init__(self):
108
 
        super(TransportProtocolAuthenticationAdapter, self).__init__()
109
 
        auth_scheme_scenarios = [
110
 
            ('basic', dict(_auth_scheme='basic')),
111
 
            ('digest', dict(_auth_scheme='digest')),
112
 
            ]
113
 
 
114
 
        self.scenarios = tests.multiply_scenarios(self.scenarios,
115
 
                                                  auth_scheme_scenarios)
116
 
 
117
 
def load_tests(standard_tests, module, loader):
118
 
    """Multiply tests for http clients and protocol versions."""
119
 
    # one for each transport
120
 
    t_adapter = TransportAdapter()
121
 
    t_classes= (TestHttpTransportRegistration,
122
 
                TestHttpTransportUrls,
 
68
 
 
69
 
 
70
load_tests = load_tests_apply_scenarios
 
71
 
 
72
 
 
73
def vary_by_http_client_implementation():
 
74
    """Test the two libraries we can use, pycurl and urllib."""
 
75
    transport_scenarios = [
 
76
        ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
 
77
                        _server=http_server.HttpServer_urllib,
 
78
                        _url_protocol='http+urllib',)),
 
79
        ]
 
80
    if features.pycurl.available():
 
81
        transport_scenarios.append(
 
82
            ('pycurl', dict(_transport=PyCurlTransport,
 
83
                            _server=http_server.HttpServer_PyCurl,
 
84
                            _url_protocol='http+pycurl',)))
 
85
    return transport_scenarios
 
86
 
 
87
 
 
88
def vary_by_http_protocol_version():
 
89
    """Test on http/1.0 and 1.1"""
 
90
    return [
 
91
        ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
 
92
        ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
 
93
        ]
 
94
 
 
95
 
 
96
def vary_by_http_auth_scheme():
 
97
    scenarios = [
 
98
        ('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
 
99
        ('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
 
100
        ('basicdigest',
 
101
            dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
 
102
        ]
 
103
    # Add some attributes common to all scenarios
 
104
    for scenario_id, scenario_dict in scenarios:
 
105
        scenario_dict.update(_auth_header='Authorization',
 
106
                             _username_prompt_prefix='',
 
107
                             _password_prompt_prefix='')
 
108
    return scenarios
 
109
 
 
110
 
 
111
def vary_by_http_proxy_auth_scheme():
 
112
    scenarios = [
 
113
        ('proxy-basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
 
114
        ('proxy-digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
 
115
        ('proxy-basicdigest',
 
116
            dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
117
        ]
 
118
    # Add some attributes common to all scenarios
 
119
    for scenario_id, scenario_dict in scenarios:
 
120
        scenario_dict.update(_auth_header='Proxy-Authorization',
 
121
                             _username_prompt_prefix='Proxy ',
 
122
                             _password_prompt_prefix='Proxy ')
 
123
    return scenarios
 
124
 
 
125
 
 
126
def vary_by_http_activity():
 
127
    activity_scenarios = [
 
128
        ('urllib,http', dict(_activity_server=ActivityHTTPServer,
 
129
                            _transport=_urllib.HttpTransport_urllib,)),
 
130
        ]
 
131
    if features.HTTPSServerFeature.available():
 
132
        activity_scenarios.append(
 
133
            ('urllib,https', dict(_activity_server=ActivityHTTPSServer,
 
134
                                _transport=_urllib.HttpTransport_urllib,)),)
 
135
    if features.pycurl.available():
 
136
        activity_scenarios.append(
 
137
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
 
138
                                _transport=PyCurlTransport,)),)
 
139
        if features.HTTPSServerFeature.available():
 
140
            from bzrlib.tests import (
 
141
                ssl_certs,
123
142
                )
124
 
    is_testing_for_transports = tests.condition_isinstance(t_classes)
125
 
 
126
 
    # multiplied by one for each protocol version
127
 
    tp_adapter = TransportProtocolAdapter()
128
 
    tp_classes= (SmartHTTPTunnellingTest,
129
 
                 TestDoCatchRedirections,
130
 
                 TestHTTPConnections,
131
 
                 TestHTTPRedirections,
132
 
                 TestHTTPSilentRedirections,
133
 
                 TestLimitedRangeRequestServer,
134
 
                 TestPost,
135
 
                 TestProxyHttpServer,
136
 
                 TestRanges,
137
 
                 TestSpecificRequestHandler,
138
 
                 )
139
 
    is_also_testing_for_protocols = tests.condition_isinstance(tp_classes)
140
 
 
141
 
    # multiplied by one for each authentication scheme
142
 
    tpa_adapter = TransportProtocolAuthenticationAdapter()
143
 
    tpa_classes = (TestAuth,
144
 
                   )
145
 
    is_also_testing_for_authentication = tests.condition_isinstance(
146
 
        tpa_classes)
147
 
 
148
 
    result = loader.suiteClass()
149
 
    for test_class in tests.iter_suite_tests(standard_tests):
150
 
        # Each test class is either standalone or testing for some combination
151
 
        # of transport, protocol version, authentication scheme. Use the right
152
 
        # adpater (or none) depending on the class.
153
 
        if is_testing_for_transports(test_class):
154
 
            result.addTests(t_adapter.adapt(test_class))
155
 
        elif is_also_testing_for_protocols(test_class):
156
 
            result.addTests(tp_adapter.adapt(test_class))
157
 
        elif is_also_testing_for_authentication(test_class):
158
 
            result.addTests(tpa_adapter.adapt(test_class))
159
 
        else:
160
 
            result.addTest(test_class)
161
 
    return result
 
143
            # FIXME: Until we have a better way to handle self-signed
 
144
            # certificates (like allowing them in a test specific
 
145
            # authentication.conf for example), we need some specialized pycurl
 
146
            # transport for tests.
 
147
            class HTTPS_pycurl_transport(PyCurlTransport):
 
148
 
 
149
                def __init__(self, base, _from_transport=None):
 
150
                    super(HTTPS_pycurl_transport, self).__init__(
 
151
                        base, _from_transport)
 
152
                    self.cabundle = str(ssl_certs.build_path('ca.crt'))
 
153
 
 
154
            activity_scenarios.append(
 
155
                ('pycurl,https', dict(_activity_server=ActivityHTTPSServer,
 
156
                                    _transport=HTTPS_pycurl_transport,)),)
 
157
    return activity_scenarios
162
158
 
163
159
 
164
160
class FakeManager(object):
172
168
 
173
169
class RecordingServer(object):
174
170
    """A fake HTTP server.
175
 
    
 
171
 
176
172
    It records the bytes sent to it, and replies with a 200.
177
173
    """
178
174
 
179
 
    def __init__(self, expect_body_tail=None):
 
175
    def __init__(self, expect_body_tail=None, scheme=''):
180
176
        """Constructor.
181
177
 
182
178
        :type expect_body_tail: str
187
183
        self.host = None
188
184
        self.port = None
189
185
        self.received_bytes = ''
190
 
 
191
 
    def setUp(self):
 
186
        self.scheme = scheme
 
187
 
 
188
    def get_url(self):
 
189
        return '%s://%s:%s/' % (self.scheme, self.host, self.port)
 
190
 
 
191
    def start_server(self):
192
192
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
193
193
        self._sock.bind(('127.0.0.1', 0))
194
194
        self.host, self.port = self._sock.getsockname()
195
195
        self._ready = threading.Event()
196
 
        self._thread = threading.Thread(target=self._accept_read_and_reply)
197
 
        self._thread.setDaemon(True)
 
196
        self._thread = test_server.TestThread(
 
197
            sync_event=self._ready, target=self._accept_read_and_reply)
198
198
        self._thread.start()
199
 
        self._ready.wait(5)
 
199
        if 'threads' in tests.selftest_debug_flags:
 
200
            sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
 
201
        self._ready.wait()
200
202
 
201
203
    def _accept_read_and_reply(self):
202
204
        self._sock.listen(1)
203
205
        self._ready.set()
204
 
        self._sock.settimeout(5)
205
 
        try:
206
 
            conn, address = self._sock.accept()
207
 
            # On win32, the accepted connection will be non-blocking to start
208
 
            # with because we're using settimeout.
209
 
            conn.setblocking(True)
 
206
        conn, address = self._sock.accept()
 
207
        if self._expect_body_tail is not None:
210
208
            while not self.received_bytes.endswith(self._expect_body_tail):
211
209
                self.received_bytes += conn.recv(4096)
212
210
            conn.sendall('HTTP/1.1 200 OK\r\n')
213
 
        except socket.timeout:
214
 
            # Make sure the client isn't stuck waiting for us to e.g. accept.
 
211
        try:
215
212
            self._sock.close()
216
213
        except socket.error:
217
214
            # The client may have already closed the socket.
218
215
            pass
219
216
 
220
 
    def tearDown(self):
 
217
    def stop_server(self):
221
218
        try:
222
 
            self._sock.close()
 
219
            # Issue a fake connection to wake up the server and allow it to
 
220
            # finish quickly
 
221
            fake_conn = osutils.connect_socket((self.host, self.port))
 
222
            fake_conn.close()
223
223
        except socket.error:
224
224
            # We might have already closed it.  We don't care.
225
225
            pass
226
226
        self.host = None
227
227
        self.port = None
 
228
        self._thread.join()
 
229
        if 'threads' in tests.selftest_debug_flags:
 
230
            sys.stderr.write('Thread  joined: %s\n' % (self._thread.ident,))
 
231
 
 
232
 
 
233
class TestAuthHeader(tests.TestCase):
 
234
 
 
235
    def parse_header(self, header, auth_handler_class=None):
 
236
        if auth_handler_class is None:
 
237
            auth_handler_class = _urllib2_wrappers.AbstractAuthHandler
 
238
        self.auth_handler =  auth_handler_class()
 
239
        return self.auth_handler._parse_auth_header(header)
 
240
 
 
241
    def test_empty_header(self):
 
242
        scheme, remainder = self.parse_header('')
 
243
        self.assertEqual('', scheme)
 
244
        self.assertIs(None, remainder)
 
245
 
 
246
    def test_negotiate_header(self):
 
247
        scheme, remainder = self.parse_header('Negotiate')
 
248
        self.assertEqual('negotiate', scheme)
 
249
        self.assertIs(None, remainder)
 
250
 
 
251
    def test_basic_header(self):
 
252
        scheme, remainder = self.parse_header(
 
253
            'Basic realm="Thou should not pass"')
 
254
        self.assertEqual('basic', scheme)
 
255
        self.assertEqual('realm="Thou should not pass"', remainder)
 
256
 
 
257
    def test_basic_extract_realm(self):
 
258
        scheme, remainder = self.parse_header(
 
259
            'Basic realm="Thou should not pass"',
 
260
            _urllib2_wrappers.BasicAuthHandler)
 
261
        match, realm = self.auth_handler.extract_realm(remainder)
 
262
        self.assertTrue(match is not None)
 
263
        self.assertEqual('Thou should not pass', realm)
 
264
 
 
265
    def test_digest_header(self):
 
266
        scheme, remainder = self.parse_header(
 
267
            'Digest realm="Thou should not pass"')
 
268
        self.assertEqual('digest', scheme)
 
269
        self.assertEqual('realm="Thou should not pass"', remainder)
 
270
 
 
271
 
 
272
class TestHTTPRangeParsing(tests.TestCase):
 
273
 
 
274
    def setUp(self):
 
275
        super(TestHTTPRangeParsing, self).setUp()
 
276
        # We focus on range  parsing here and ignore everything else
 
277
        class RequestHandler(http_server.TestingHTTPRequestHandler):
 
278
            def setup(self): pass
 
279
            def handle(self): pass
 
280
            def finish(self): pass
 
281
 
 
282
        self.req_handler = RequestHandler(None, None, None)
 
283
 
 
284
    def assertRanges(self, ranges, header, file_size):
 
285
        self.assertEquals(ranges,
 
286
                          self.req_handler._parse_ranges(header, file_size))
 
287
 
 
288
    def test_simple_range(self):
 
289
        self.assertRanges([(0,2)], 'bytes=0-2', 12)
 
290
 
 
291
    def test_tail(self):
 
292
        self.assertRanges([(8, 11)], 'bytes=-4', 12)
 
293
 
 
294
    def test_tail_bigger_than_file(self):
 
295
        self.assertRanges([(0, 11)], 'bytes=-99', 12)
 
296
 
 
297
    def test_range_without_end(self):
 
298
        self.assertRanges([(4, 11)], 'bytes=4-', 12)
 
299
 
 
300
    def test_invalid_ranges(self):
 
301
        self.assertRanges(None, 'bytes=12-22', 12)
 
302
        self.assertRanges(None, 'bytes=1-3,12-22', 12)
 
303
        self.assertRanges(None, 'bytes=-', 12)
228
304
 
229
305
 
230
306
class TestHTTPServer(tests.TestCase):
235
311
 
236
312
            protocol_version = 'HTTP/0.1'
237
313
 
238
 
        server = http_server.HttpServer(BogusRequestHandler)
239
 
        try:
240
 
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
241
 
        except:
242
 
            server.tearDown()
243
 
            self.fail('HTTP Server creation did not raise UnknownProtocol')
 
314
        self.assertRaises(httplib.UnknownProtocol,
 
315
                          http_server.HttpServer, BogusRequestHandler)
244
316
 
245
317
    def test_force_invalid_protocol(self):
246
 
        server = http_server.HttpServer(protocol_version='HTTP/0.1')
247
 
        try:
248
 
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
249
 
        except:
250
 
            server.tearDown()
251
 
            self.fail('HTTP Server creation did not raise UnknownProtocol')
 
318
        self.assertRaises(httplib.UnknownProtocol,
 
319
                          http_server.HttpServer, protocol_version='HTTP/0.1')
252
320
 
253
321
    def test_server_start_and_stop(self):
254
322
        server = http_server.HttpServer()
255
 
        server.setUp()
256
 
        self.assertTrue(server._http_running)
257
 
        server.tearDown()
258
 
        self.assertFalse(server._http_running)
 
323
        self.addCleanup(server.stop_server)
 
324
        server.start_server()
 
325
        self.assertTrue(server.server is not None)
 
326
        self.assertTrue(server.server.serving is not None)
 
327
        self.assertTrue(server.server.serving)
259
328
 
260
329
    def test_create_http_server_one_zero(self):
261
330
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
263
332
            protocol_version = 'HTTP/1.0'
264
333
 
265
334
        server = http_server.HttpServer(RequestHandlerOneZero)
266
 
        server.setUp()
267
 
        self.addCleanup(server.tearDown)
268
 
        self.assertIsInstance(server._httpd, http_server.TestingHTTPServer)
 
335
        self.start_server(server)
 
336
        self.assertIsInstance(server.server, http_server.TestingHTTPServer)
269
337
 
270
338
    def test_create_http_server_one_one(self):
271
339
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
273
341
            protocol_version = 'HTTP/1.1'
274
342
 
275
343
        server = http_server.HttpServer(RequestHandlerOneOne)
276
 
        server.setUp()
277
 
        self.addCleanup(server.tearDown)
278
 
        self.assertIsInstance(server._httpd,
 
344
        self.start_server(server)
 
345
        self.assertIsInstance(server.server,
279
346
                              http_server.TestingThreadingHTTPServer)
280
347
 
281
348
    def test_create_http_server_force_one_one(self):
285
352
 
286
353
        server = http_server.HttpServer(RequestHandlerOneZero,
287
354
                                        protocol_version='HTTP/1.1')
288
 
        server.setUp()
289
 
        self.addCleanup(server.tearDown)
290
 
        self.assertIsInstance(server._httpd,
 
355
        self.start_server(server)
 
356
        self.assertIsInstance(server.server,
291
357
                              http_server.TestingThreadingHTTPServer)
292
358
 
293
359
    def test_create_http_server_force_one_zero(self):
297
363
 
298
364
        server = http_server.HttpServer(RequestHandlerOneOne,
299
365
                                        protocol_version='HTTP/1.0')
300
 
        server.setUp()
301
 
        self.addCleanup(server.tearDown)
302
 
        self.assertIsInstance(server._httpd,
 
366
        self.start_server(server)
 
367
        self.assertIsInstance(server.server,
303
368
                              http_server.TestingHTTPServer)
304
369
 
305
370
 
307
372
    """Test case to inherit from if pycurl is present"""
308
373
 
309
374
    def _get_pycurl_maybe(self):
310
 
        try:
311
 
            from bzrlib.transport.http._pycurl import PyCurlTransport
312
 
            return PyCurlTransport
313
 
        except errors.DependencyNotPresent:
314
 
            raise tests.TestSkipped('pycurl not present')
 
375
        self.requireFeature(features.pycurl)
 
376
        return PyCurlTransport
315
377
 
316
378
    _transport = property(_get_pycurl_maybe)
317
379
 
324
386
    def test_url_parsing(self):
325
387
        f = FakeManager()
326
388
        url = http.extract_auth('http://example.com', f)
327
 
        self.assertEquals('http://example.com', url)
328
 
        self.assertEquals(0, len(f.credentials))
 
389
        self.assertEqual('http://example.com', url)
 
390
        self.assertEqual(0, len(f.credentials))
329
391
        url = http.extract_auth(
330
 
            'http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
331
 
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
332
 
        self.assertEquals(1, len(f.credentials))
333
 
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
334
 
                          f.credentials[0])
 
392
            'http://user:pass@example.com/bzr/bzr.dev', f)
 
393
        self.assertEqual('http://example.com/bzr/bzr.dev', url)
 
394
        self.assertEqual(1, len(f.credentials))
 
395
        self.assertEqual([None, 'example.com', 'user', 'pass'],
 
396
                         f.credentials[0])
335
397
 
336
398
 
337
399
class TestHttpTransportUrls(tests.TestCase):
338
400
    """Test the http urls."""
339
401
 
 
402
    scenarios = vary_by_http_client_implementation()
 
403
 
340
404
    def test_abs_url(self):
341
405
        """Construction of absolute http URLs"""
342
 
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
406
        t = self._transport('http://example.com/bzr/bzr.dev/')
343
407
        eq = self.assertEqualDiff
344
 
        eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
345
 
        eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
346
 
        eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
 
408
        eq(t.abspath('.'), 'http://example.com/bzr/bzr.dev')
 
409
        eq(t.abspath('foo/bar'), 'http://example.com/bzr/bzr.dev/foo/bar')
 
410
        eq(t.abspath('.bzr'), 'http://example.com/bzr/bzr.dev/.bzr')
347
411
        eq(t.abspath('.bzr/1//2/./3'),
348
 
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
 
412
           'http://example.com/bzr/bzr.dev/.bzr/1/2/3')
349
413
 
350
414
    def test_invalid_http_urls(self):
351
415
        """Trap invalid construction of urls"""
352
 
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
416
        self._transport('http://example.com/bzr/bzr.dev/')
353
417
        self.assertRaises(errors.InvalidURL,
354
418
                          self._transport,
355
 
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
 
419
                          'http://http://example.com/bzr/bzr.dev/')
356
420
 
357
421
    def test_http_root_urls(self):
358
422
        """Construction of URLs from server root"""
359
 
        t = self._transport('http://bzr.ozlabs.org/')
 
423
        t = self._transport('http://example.com/')
360
424
        eq = self.assertEqualDiff
361
425
        eq(t.abspath('.bzr/tree-version'),
362
 
           'http://bzr.ozlabs.org/.bzr/tree-version')
 
426
           'http://example.com/.bzr/tree-version')
363
427
 
364
428
    def test_http_impl_urls(self):
365
429
        """There are servers which ask for particular clients to connect"""
366
430
        server = self._server()
 
431
        server.start_server()
367
432
        try:
368
 
            server.setUp()
369
433
            url = server.get_url()
370
 
            self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
 
434
            self.assertTrue(url.startswith('%s://' % self._url_protocol))
371
435
        finally:
372
 
            server.tearDown()
 
436
            server.stop_server()
373
437
 
374
438
 
375
439
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
384
448
        https by supplying a fake version_info that do not
385
449
        support it.
386
450
        """
387
 
        try:
388
 
            import pycurl
389
 
        except ImportError:
390
 
            raise tests.TestSkipped('pycurl not present')
 
451
        self.requireFeature(features.pycurl)
 
452
        # Import the module locally now that we now it's available.
 
453
        pycurl = features.pycurl.module
391
454
 
392
 
        version_info_orig = pycurl.version_info
393
 
        try:
394
 
            # Now that we have pycurl imported, we can fake its version_info
395
 
            # This was taken from a windows pycurl without SSL
396
 
            # (thanks to bialix)
397
 
            pycurl.version_info = lambda : (2,
398
 
                                            '7.13.2',
399
 
                                            462082,
400
 
                                            'i386-pc-win32',
401
 
                                            2576,
402
 
                                            None,
403
 
                                            0,
404
 
                                            None,
405
 
                                            ('ftp', 'gopher', 'telnet',
406
 
                                             'dict', 'ldap', 'http', 'file'),
407
 
                                            None,
408
 
                                            0,
409
 
                                            None)
410
 
            self.assertRaises(errors.DependencyNotPresent, self._transport,
411
 
                              'https://launchpad.net')
412
 
        finally:
413
 
            # Restore the right function
414
 
            pycurl.version_info = version_info_orig
 
455
        self.overrideAttr(pycurl, 'version_info',
 
456
                          # Fake the pycurl version_info This was taken from
 
457
                          # a windows pycurl without SSL (thanks to bialix)
 
458
                          lambda : (2,
 
459
                                    '7.13.2',
 
460
                                    462082,
 
461
                                    'i386-pc-win32',
 
462
                                    2576,
 
463
                                    None,
 
464
                                    0,
 
465
                                    None,
 
466
                                    ('ftp', 'gopher', 'telnet',
 
467
                                     'dict', 'ldap', 'http', 'file'),
 
468
                                    None,
 
469
                                    0,
 
470
                                    None))
 
471
        self.assertRaises(errors.DependencyNotPresent, self._transport,
 
472
                          'https://launchpad.net')
415
473
 
416
474
 
417
475
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
418
476
    """Test the http connections."""
419
477
 
 
478
    scenarios = multiply_scenarios(
 
479
        vary_by_http_client_implementation(),
 
480
        vary_by_http_protocol_version(),
 
481
        )
 
482
 
420
483
    def setUp(self):
421
484
        http_utils.TestCaseWithWebserver.setUp(self)
422
485
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
424
487
 
425
488
    def test_http_has(self):
426
489
        server = self.get_readonly_server()
427
 
        t = self._transport(server.get_url())
 
490
        t = self.get_readonly_transport()
428
491
        self.assertEqual(t.has('foo/bar'), True)
429
492
        self.assertEqual(len(server.logs), 1)
430
493
        self.assertContainsRe(server.logs[0],
432
495
 
433
496
    def test_http_has_not_found(self):
434
497
        server = self.get_readonly_server()
435
 
        t = self._transport(server.get_url())
 
498
        t = self.get_readonly_transport()
436
499
        self.assertEqual(t.has('not-found'), False)
437
500
        self.assertContainsRe(server.logs[1],
438
501
            r'"HEAD /not-found HTTP/1.." 404 - "-" "bzr/')
439
502
 
440
503
    def test_http_get(self):
441
504
        server = self.get_readonly_server()
442
 
        t = self._transport(server.get_url())
 
505
        t = self.get_readonly_transport()
443
506
        fp = t.get('foo/bar')
444
507
        self.assertEqualDiff(
445
508
            fp.read(),
467
530
class TestHttpTransportRegistration(tests.TestCase):
468
531
    """Test registrations of various http implementations"""
469
532
 
 
533
    scenarios = vary_by_http_client_implementation()
 
534
 
470
535
    def test_http_registered(self):
471
 
        t = transport.get_transport('%s://foo.com/' % self._qualified_prefix)
 
536
        t = transport.get_transport('%s://foo.com/' % self._url_protocol)
472
537
        self.assertIsInstance(t, transport.Transport)
473
538
        self.assertIsInstance(t, self._transport)
474
539
 
475
540
 
476
541
class TestPost(tests.TestCase):
477
542
 
 
543
    scenarios = multiply_scenarios(
 
544
        vary_by_http_client_implementation(),
 
545
        vary_by_http_protocol_version(),
 
546
        )
 
547
 
478
548
    def test_post_body_is_received(self):
479
 
        server = RecordingServer(expect_body_tail='end-of-body')
480
 
        server.setUp()
481
 
        self.addCleanup(server.tearDown)
482
 
        scheme = self._qualified_prefix
483
 
        url = '%s://%s:%s/' % (scheme, server.host, server.port)
484
 
        http_transport = self._transport(url)
 
549
        server = RecordingServer(expect_body_tail='end-of-body',
 
550
                                 scheme=self._url_protocol)
 
551
        self.start_server(server)
 
552
        url = server.get_url()
 
553
        # FIXME: needs a cleanup -- vila 20100611
 
554
        http_transport = transport.get_transport(url)
485
555
        code, response = http_transport._post('abc def end-of-body')
486
556
        self.assertTrue(
487
557
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
488
558
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
 
559
        self.assertTrue('content-type: application/octet-stream\r'
 
560
                        in server.received_bytes.lower())
489
561
        # The transport should not be assuming that the server can accept
490
562
        # chunked encoding the first time it connects, because HTTP/1.1, so we
491
563
        # check for the literal string.
527
599
    Daughter classes are expected to override _req_handler_class
528
600
    """
529
601
 
 
602
    scenarios = multiply_scenarios(
 
603
        vary_by_http_client_implementation(),
 
604
        vary_by_http_protocol_version(),
 
605
        )
 
606
 
530
607
    # Provide a useful default
531
608
    _req_handler_class = http_server.TestingHTTPRequestHandler
532
609
 
533
610
    def create_transport_readonly_server(self):
534
 
        return http_server.HttpServer(self._req_handler_class,
535
 
                                      protocol_version=self._protocol_version)
 
611
        server = http_server.HttpServer(self._req_handler_class,
 
612
                                        protocol_version=self._protocol_version)
 
613
        server._url_protocol = self._url_protocol
 
614
        return server
536
615
 
537
616
    def _testing_pycurl(self):
538
 
        return pycurl_present and self._transport == PyCurlTransport
 
617
        # TODO: This is duplicated for lots of the classes in this file
 
618
        return (features.pycurl.available()
 
619
                and self._transport == PyCurlTransport)
539
620
 
540
621
 
541
622
class WallRequestHandler(http_server.TestingHTTPRequestHandler):
542
623
    """Whatever request comes in, close the connection"""
543
624
 
544
 
    def handle_one_request(self):
 
625
    def _handle_one_request(self):
545
626
        """Handle a single HTTP request, by abruptly closing the connection"""
546
627
        self.close_connection = 1
547
628
 
552
633
    _req_handler_class = WallRequestHandler
553
634
 
554
635
    def test_http_has(self):
555
 
        server = self.get_readonly_server()
556
 
        t = self._transport(server.get_url())
 
636
        t = self.get_readonly_transport()
557
637
        # Unfortunately httplib (see HTTPResponse._read_status
558
638
        # for details) make no distinction between a closed
559
639
        # socket and badly formatted status line, so we can't
560
640
        # just test for ConnectionError, we have to test
561
 
        # InvalidHttpResponse too.
562
 
        self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
 
641
        # InvalidHttpResponse too. And pycurl may raise ConnectionReset
 
642
        # instead of ConnectionError too.
 
643
        self.assertRaises(( errors.ConnectionError, errors.ConnectionReset,
 
644
                            errors.InvalidHttpResponse),
563
645
                          t.has, 'foo/bar')
564
646
 
565
647
    def test_http_get(self):
566
 
        server = self.get_readonly_server()
567
 
        t = self._transport(server.get_url())
568
 
        self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
 
648
        t = self.get_readonly_transport()
 
649
        self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
 
650
                           errors.InvalidHttpResponse),
569
651
                          t.get, 'foo/bar')
570
652
 
571
653
 
586
668
    _req_handler_class = BadStatusRequestHandler
587
669
 
588
670
    def test_http_has(self):
589
 
        server = self.get_readonly_server()
590
 
        t = self._transport(server.get_url())
 
671
        t = self.get_readonly_transport()
591
672
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
592
673
 
593
674
    def test_http_get(self):
594
 
        server = self.get_readonly_server()
595
 
        t = self._transport(server.get_url())
 
675
        t = self.get_readonly_transport()
596
676
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
597
677
 
598
678
 
603
683
        """Fakes handling a single HTTP request, returns a bad status"""
604
684
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
605
685
        self.wfile.write("Invalid status line\r\n")
 
686
        # If we don't close the connection pycurl will hang. Since this is a
 
687
        # stress test we don't *have* to respect the protocol, but we don't
 
688
        # have to sabotage it too much either.
 
689
        self.close_connection = True
606
690
        return False
607
691
 
608
692
 
614
698
 
615
699
    _req_handler_class = InvalidStatusRequestHandler
616
700
 
617
 
    def test_http_has(self):
618
 
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
619
 
            raise tests.KnownFailure(
620
 
                'pycurl hangs if the server send back garbage')
621
 
        super(TestInvalidStatusServer, self).test_http_has()
622
 
 
623
 
    def test_http_get(self):
624
 
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
625
 
            raise tests.KnownFailure(
626
 
                'pycurl hangs if the server send back garbage')
627
 
        super(TestInvalidStatusServer, self).test_http_get()
628
 
 
629
701
 
630
702
class BadProtocolRequestHandler(http_server.TestingHTTPRequestHandler):
631
703
    """Whatever request comes in, returns a bad protocol version"""
647
719
    _req_handler_class = BadProtocolRequestHandler
648
720
 
649
721
    def setUp(self):
650
 
        if pycurl_present and self._transport == PyCurlTransport:
 
722
        if self._testing_pycurl():
651
723
            raise tests.TestNotApplicable(
652
724
                "pycurl doesn't check the protocol version")
653
725
        super(TestBadProtocolServer, self).setUp()
654
726
 
655
727
    def test_http_has(self):
656
 
        server = self.get_readonly_server()
657
 
        t = self._transport(server.get_url())
 
728
        t = self.get_readonly_transport()
658
729
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
659
730
 
660
731
    def test_http_get(self):
661
 
        server = self.get_readonly_server()
662
 
        t = self._transport(server.get_url())
 
732
        t = self.get_readonly_transport()
663
733
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
664
734
 
665
735
 
679
749
    _req_handler_class = ForbiddenRequestHandler
680
750
 
681
751
    def test_http_has(self):
682
 
        server = self.get_readonly_server()
683
 
        t = self._transport(server.get_url())
 
752
        t = self.get_readonly_transport()
684
753
        self.assertRaises(errors.TransportError, t.has, 'foo/bar')
685
754
 
686
755
    def test_http_get(self):
687
 
        server = self.get_readonly_server()
688
 
        t = self._transport(server.get_url())
 
756
        t = self.get_readonly_transport()
689
757
        self.assertRaises(errors.TransportError, t.get, 'foo/bar')
690
758
 
691
759
 
697
765
        self.assertEqual(None, server.host)
698
766
        self.assertEqual(None, server.port)
699
767
 
700
 
    def test_setUp_and_tearDown(self):
 
768
    def test_setUp_and_stop(self):
701
769
        server = RecordingServer(expect_body_tail=None)
702
 
        server.setUp()
 
770
        server.start_server()
703
771
        try:
704
772
            self.assertNotEqual(None, server.host)
705
773
            self.assertNotEqual(None, server.port)
706
774
        finally:
707
 
            server.tearDown()
 
775
            server.stop_server()
708
776
        self.assertEqual(None, server.host)
709
777
        self.assertEqual(None, server.port)
710
778
 
711
779
    def test_send_receive_bytes(self):
712
 
        server = RecordingServer(expect_body_tail='c')
713
 
        server.setUp()
714
 
        self.addCleanup(server.tearDown)
 
780
        server = RecordingServer(expect_body_tail='c', scheme='http')
 
781
        self.start_server(server)
715
782
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
716
783
        sock.connect((server.host, server.port))
717
784
        sock.sendall('abc')
731
798
        self.build_tree_contents([('a', '0123456789')],)
732
799
 
733
800
    def test_readv(self):
734
 
        server = self.get_readonly_server()
735
 
        t = self._transport(server.get_url())
 
801
        t = self.get_readonly_transport()
736
802
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
737
803
        self.assertEqual(l[0], (0, '0'))
738
804
        self.assertEqual(l[1], (1, '1'))
740
806
        self.assertEqual(l[3], (9, '9'))
741
807
 
742
808
    def test_readv_out_of_order(self):
743
 
        server = self.get_readonly_server()
744
 
        t = self._transport(server.get_url())
 
809
        t = self.get_readonly_transport()
745
810
        l = list(t.readv('a', ((1, 1), (9, 1), (0, 1), (3, 2))))
746
811
        self.assertEqual(l[0], (1, '1'))
747
812
        self.assertEqual(l[1], (9, '9'))
749
814
        self.assertEqual(l[3], (3, '34'))
750
815
 
751
816
    def test_readv_invalid_ranges(self):
752
 
        server = self.get_readonly_server()
753
 
        t = self._transport(server.get_url())
 
817
        t = self.get_readonly_transport()
754
818
 
755
819
        # This is intentionally reading off the end of the file
756
820
        # since we are sure that it cannot get there
764
828
 
765
829
    def test_readv_multiple_get_requests(self):
766
830
        server = self.get_readonly_server()
767
 
        t = self._transport(server.get_url())
 
831
        t = self.get_readonly_transport()
768
832
        # force transport to issue multiple requests
769
833
        t._max_readv_combine = 1
770
834
        t._max_get_ranges = 1
778
842
 
779
843
    def test_readv_get_max_size(self):
780
844
        server = self.get_readonly_server()
781
 
        t = self._transport(server.get_url())
 
845
        t = self.get_readonly_transport()
782
846
        # force transport to issue multiple requests by limiting the number of
783
847
        # bytes by request. Note that this apply to coalesced offsets only, a
784
848
        # single range will keep its size even if bigger than the limit.
793
857
 
794
858
    def test_complete_readv_leave_pipe_clean(self):
795
859
        server = self.get_readonly_server()
796
 
        t = self._transport(server.get_url())
 
860
        t = self.get_readonly_transport()
797
861
        # force transport to issue multiple requests
798
862
        t._get_max_size = 2
799
 
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
863
        list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
800
864
        # The server should have issued 3 requests
801
865
        self.assertEqual(3, server.GET_request_nb)
802
866
        self.assertEqual('0123456789', t.get_bytes('a'))
804
868
 
805
869
    def test_incomplete_readv_leave_pipe_clean(self):
806
870
        server = self.get_readonly_server()
807
 
        t = self._transport(server.get_url())
 
871
        t = self.get_readonly_transport()
808
872
        # force transport to issue multiple requests
809
873
        t._get_max_size = 2
810
874
        # Don't collapse readv results into a list so that we leave unread
811
875
        # bytes on the socket
812
876
        ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
813
877
        self.assertEqual((0, '0'), ireadv.next())
814
 
        # The server should have issued one request so far 
 
878
        # The server should have issued one request so far
815
879
        self.assertEqual(1, server.GET_request_nb)
816
880
        self.assertEqual('0123456789', t.get_bytes('a'))
817
881
        # get_bytes issued an additional request, the readv pending ones are
879
943
    def get_multiple_ranges(self, file, file_size, ranges):
880
944
        self.send_response(206)
881
945
        self.send_header('Accept-Ranges', 'bytes')
 
946
        # XXX: this is strange; the 'random' name below seems undefined and
 
947
        # yet the tests pass -- mbp 2010-10-11 bug 658773
882
948
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
883
949
        self.send_header("Content-Type",
884
950
                         "multipart/byteranges; boundary=%s" % boundary)
946
1012
                return
947
1013
            self.send_range_content(file, start, end - start + 1)
948
1014
            cur += 1
949
 
        # No final boundary
 
1015
        # Final boundary
950
1016
        self.wfile.write(boundary_line)
951
1017
 
952
1018
 
960
1026
 
961
1027
    def test_readv_with_short_reads(self):
962
1028
        server = self.get_readonly_server()
963
 
        t = self._transport(server.get_url())
 
1029
        t = self.get_readonly_transport()
964
1030
        # Force separate ranges for each offset
965
1031
        t._bytes_to_read_before_seek = 0
966
1032
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
981
1047
        # that mode
982
1048
        self.assertEqual('single', t._range_hint)
983
1049
 
 
1050
 
 
1051
class TruncatedBeforeBoundaryRequestHandler(
 
1052
    http_server.TestingHTTPRequestHandler):
 
1053
    """Truncation before a boundary, like in bug 198646"""
 
1054
 
 
1055
    _truncated_ranges = 1
 
1056
 
 
1057
    def get_multiple_ranges(self, file, file_size, ranges):
 
1058
        self.send_response(206)
 
1059
        self.send_header('Accept-Ranges', 'bytes')
 
1060
        boundary = 'tagada'
 
1061
        self.send_header('Content-Type',
 
1062
                         'multipart/byteranges; boundary=%s' % boundary)
 
1063
        boundary_line = '--%s\r\n' % boundary
 
1064
        # Calculate the Content-Length
 
1065
        content_length = 0
 
1066
        for (start, end) in ranges:
 
1067
            content_length += len(boundary_line)
 
1068
            content_length += self._header_line_length(
 
1069
                'Content-type', 'application/octet-stream')
 
1070
            content_length += self._header_line_length(
 
1071
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
 
1072
            content_length += len('\r\n') # end headers
 
1073
            content_length += end - start # + 1
 
1074
        content_length += len(boundary_line)
 
1075
        self.send_header('Content-length', content_length)
 
1076
        self.end_headers()
 
1077
 
 
1078
        # Send the multipart body
 
1079
        cur = 0
 
1080
        for (start, end) in ranges:
 
1081
            if cur + self._truncated_ranges >= len(ranges):
 
1082
                # Abruptly ends the response and close the connection
 
1083
                self.close_connection = 1
 
1084
                return
 
1085
            self.wfile.write(boundary_line)
 
1086
            self.send_header('Content-type', 'application/octet-stream')
 
1087
            self.send_header('Content-Range', 'bytes %d-%d/%d'
 
1088
                             % (start, end, file_size))
 
1089
            self.end_headers()
 
1090
            self.send_range_content(file, start, end - start + 1)
 
1091
            cur += 1
 
1092
        # Final boundary
 
1093
        self.wfile.write(boundary_line)
 
1094
 
 
1095
 
 
1096
class TestTruncatedBeforeBoundary(TestSpecificRequestHandler):
 
1097
    """Tests the case of bug 198646, disconnecting before a boundary."""
 
1098
 
 
1099
    _req_handler_class = TruncatedBeforeBoundaryRequestHandler
 
1100
 
 
1101
    def setUp(self):
 
1102
        super(TestTruncatedBeforeBoundary, self).setUp()
 
1103
        self.build_tree_contents([('a', '0123456789')],)
 
1104
 
 
1105
    def test_readv_with_short_reads(self):
 
1106
        server = self.get_readonly_server()
 
1107
        t = self.get_readonly_transport()
 
1108
        # Force separate ranges for each offset
 
1109
        t._bytes_to_read_before_seek = 0
 
1110
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
 
1111
        self.assertEqual((0, '0'), ireadv.next())
 
1112
        self.assertEqual((2, '2'), ireadv.next())
 
1113
        self.assertEqual((4, '45'), ireadv.next())
 
1114
        self.assertEqual((9, '9'), ireadv.next())
 
1115
 
 
1116
 
984
1117
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
985
1118
    """Errors out when range specifiers exceed the limit"""
986
1119
 
1010
1143
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
1011
1144
    """Tests readv requests against a server erroring out on too much ranges."""
1012
1145
 
 
1146
    scenarios = multiply_scenarios(
 
1147
        vary_by_http_client_implementation(),
 
1148
        vary_by_http_protocol_version(),
 
1149
        )
 
1150
 
1013
1151
    # Requests with more range specifiers will error out
1014
1152
    range_limit = 3
1015
1153
 
1017
1155
        return LimitedRangeHTTPServer(range_limit=self.range_limit,
1018
1156
                                      protocol_version=self._protocol_version)
1019
1157
 
1020
 
    def get_transport(self):
1021
 
        return self._transport(self.get_readonly_server().get_url())
1022
 
 
1023
1158
    def setUp(self):
1024
1159
        http_utils.TestCaseWithWebserver.setUp(self)
1025
1160
        # We need to manipulate ranges that correspond to real chunks in the
1029
1164
        self.build_tree_contents([('a', content)],)
1030
1165
 
1031
1166
    def test_few_ranges(self):
1032
 
        t = self.get_transport()
 
1167
        t = self.get_readonly_transport()
1033
1168
        l = list(t.readv('a', ((0, 4), (1024, 4), )))
1034
1169
        self.assertEqual(l[0], (0, '0000'))
1035
1170
        self.assertEqual(l[1], (1024, '0001'))
1036
1171
        self.assertEqual(1, self.get_readonly_server().GET_request_nb)
1037
1172
 
1038
1173
    def test_more_ranges(self):
1039
 
        t = self.get_transport()
 
1174
        t = self.get_readonly_transport()
1040
1175
        l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
1041
1176
        self.assertEqual(l[0], (0, '0000'))
1042
1177
        self.assertEqual(l[1], (1024, '0001'))
1053
1188
    Only the urllib implementation is tested here.
1054
1189
    """
1055
1190
 
1056
 
    def setUp(self):
1057
 
        tests.TestCase.setUp(self)
1058
 
        self._old_env = {}
1059
 
 
1060
 
    def tearDown(self):
1061
 
        self._restore_env()
1062
 
        tests.TestCase.tearDown(self)
1063
 
 
1064
 
    def _install_env(self, env):
1065
 
        for name, value in env.iteritems():
1066
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1067
 
 
1068
 
    def _restore_env(self):
1069
 
        for name, value in self._old_env.iteritems():
1070
 
            osutils.set_or_unset_env(name, value)
1071
 
 
1072
1191
    def _proxied_request(self):
1073
1192
        handler = _urllib2_wrappers.ProxyHandler()
1074
 
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
 
1193
        request = _urllib2_wrappers.Request('GET', 'http://baz/buzzle')
1075
1194
        handler.set_proxy(request, 'http')
1076
1195
        return request
1077
1196
 
 
1197
    def assertEvaluateProxyBypass(self, expected, host, no_proxy):
 
1198
        handler = _urllib2_wrappers.ProxyHandler()
 
1199
        self.assertEquals(expected,
 
1200
                          handler.evaluate_proxy_bypass(host, no_proxy))
 
1201
 
1078
1202
    def test_empty_user(self):
1079
 
        self._install_env({'http_proxy': 'http://bar.com'})
 
1203
        self.overrideEnv('http_proxy', 'http://bar.com')
 
1204
        request = self._proxied_request()
 
1205
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
 
1206
 
 
1207
    def test_user_with_at(self):
 
1208
        self.overrideEnv('http_proxy',
 
1209
                         'http://username@domain:password@proxy_host:1234')
1080
1210
        request = self._proxied_request()
1081
1211
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
1082
1212
 
1083
1213
    def test_invalid_proxy(self):
1084
1214
        """A proxy env variable without scheme"""
1085
 
        self._install_env({'http_proxy': 'host:1234'})
 
1215
        self.overrideEnv('http_proxy', 'host:1234')
1086
1216
        self.assertRaises(errors.InvalidURL, self._proxied_request)
1087
1217
 
 
1218
    def test_evaluate_proxy_bypass_true(self):
 
1219
        """The host is not proxied"""
 
1220
        self.assertEvaluateProxyBypass(True, 'example.com', 'example.com')
 
1221
        self.assertEvaluateProxyBypass(True, 'bzr.example.com', '*example.com')
 
1222
 
 
1223
    def test_evaluate_proxy_bypass_false(self):
 
1224
        """The host is proxied"""
 
1225
        self.assertEvaluateProxyBypass(False, 'bzr.example.com', None)
 
1226
 
 
1227
    def test_evaluate_proxy_bypass_unknown(self):
 
1228
        """The host is not explicitly proxied"""
 
1229
        self.assertEvaluateProxyBypass(None, 'example.com', 'not.example.com')
 
1230
        self.assertEvaluateProxyBypass(None, 'bzr.example.com', 'example.com')
 
1231
 
 
1232
    def test_evaluate_proxy_bypass_empty_entries(self):
 
1233
        """Ignore empty entries"""
 
1234
        self.assertEvaluateProxyBypass(None, 'example.com', '')
 
1235
        self.assertEvaluateProxyBypass(None, 'example.com', ',')
 
1236
        self.assertEvaluateProxyBypass(None, 'example.com', 'foo,,bar')
 
1237
 
1088
1238
 
1089
1239
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
1090
1240
    """Tests proxy server.
1095
1245
    to the file names).
1096
1246
    """
1097
1247
 
 
1248
    scenarios = multiply_scenarios(
 
1249
        vary_by_http_client_implementation(),
 
1250
        vary_by_http_protocol_version(),
 
1251
        )
 
1252
 
1098
1253
    # FIXME: We don't have an https server available, so we don't
1099
 
    # test https connections.
 
1254
    # test https connections. --vila toolongago
1100
1255
 
1101
1256
    def setUp(self):
1102
1257
        super(TestProxyHttpServer, self).setUp()
 
1258
        self.transport_secondary_server = http_utils.ProxyServer
1103
1259
        self.build_tree_contents([('foo', 'contents of foo\n'),
1104
1260
                                  ('foo-proxied', 'proxied contents of foo\n')])
1105
1261
        # Let's setup some attributes for tests
1106
 
        self.server = self.get_readonly_server()
1107
 
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
 
1262
        server = self.get_readonly_server()
 
1263
        self.server_host_port = '%s:%d' % (server.host, server.port)
1108
1264
        if self._testing_pycurl():
1109
1265
            # Oh my ! pycurl does not check for the port as part of
1110
1266
            # no_proxy :-( So we just test the host part
1111
 
            self.no_proxy_host = 'localhost'
 
1267
            self.no_proxy_host = server.host
1112
1268
        else:
1113
 
            self.no_proxy_host = self.proxy_address
 
1269
            self.no_proxy_host = self.server_host_port
1114
1270
        # The secondary server is the proxy
1115
 
        self.proxy = self.get_secondary_server()
1116
 
        self.proxy_url = self.proxy.get_url()
1117
 
        self._old_env = {}
 
1271
        self.proxy_url = self.get_secondary_url()
1118
1272
 
1119
1273
    def _testing_pycurl(self):
1120
 
        return pycurl_present and self._transport == PyCurlTransport
1121
 
 
1122
 
    def create_transport_secondary_server(self):
1123
 
        """Creates an http server that will serve files with
1124
 
        '-proxied' appended to their names.
1125
 
        """
1126
 
        return http_utils.ProxyServer(protocol_version=self._protocol_version)
1127
 
 
1128
 
    def _install_env(self, env):
1129
 
        for name, value in env.iteritems():
1130
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1131
 
 
1132
 
    def _restore_env(self):
1133
 
        for name, value in self._old_env.iteritems():
1134
 
            osutils.set_or_unset_env(name, value)
1135
 
 
1136
 
    def proxied_in_env(self, env):
1137
 
        self._install_env(env)
1138
 
        url = self.server.get_url()
1139
 
        t = self._transport(url)
1140
 
        try:
1141
 
            self.assertEqual('proxied contents of foo\n', t.get('foo').read())
1142
 
        finally:
1143
 
            self._restore_env()
1144
 
 
1145
 
    def not_proxied_in_env(self, env):
1146
 
        self._install_env(env)
1147
 
        url = self.server.get_url()
1148
 
        t = self._transport(url)
1149
 
        try:
1150
 
            self.assertEqual('contents of foo\n', t.get('foo').read())
1151
 
        finally:
1152
 
            self._restore_env()
 
1274
        # TODO: This is duplicated for lots of the classes in this file
 
1275
        return (features.pycurl.available()
 
1276
                and self._transport == PyCurlTransport)
 
1277
 
 
1278
    def assertProxied(self):
 
1279
        t = self.get_readonly_transport()
 
1280
        self.assertEqual('proxied contents of foo\n', t.get('foo').read())
 
1281
 
 
1282
    def assertNotProxied(self):
 
1283
        t = self.get_readonly_transport()
 
1284
        self.assertEqual('contents of foo\n', t.get('foo').read())
1153
1285
 
1154
1286
    def test_http_proxy(self):
1155
 
        self.proxied_in_env({'http_proxy': self.proxy_url})
 
1287
        self.overrideEnv('http_proxy', self.proxy_url)
 
1288
        self.assertProxied()
1156
1289
 
1157
1290
    def test_HTTP_PROXY(self):
1158
1291
        if self._testing_pycurl():
1161
1294
            # about. Should we ?)
1162
1295
            raise tests.TestNotApplicable(
1163
1296
                'pycurl does not check HTTP_PROXY for security reasons')
1164
 
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
 
1297
        self.overrideEnv('HTTP_PROXY', self.proxy_url)
 
1298
        self.assertProxied()
1165
1299
 
1166
1300
    def test_all_proxy(self):
1167
 
        self.proxied_in_env({'all_proxy': self.proxy_url})
 
1301
        self.overrideEnv('all_proxy', self.proxy_url)
 
1302
        self.assertProxied()
1168
1303
 
1169
1304
    def test_ALL_PROXY(self):
1170
 
        self.proxied_in_env({'ALL_PROXY': self.proxy_url})
 
1305
        self.overrideEnv('ALL_PROXY', self.proxy_url)
 
1306
        self.assertProxied()
1171
1307
 
1172
1308
    def test_http_proxy_with_no_proxy(self):
1173
 
        self.not_proxied_in_env({'http_proxy': self.proxy_url,
1174
 
                                 'no_proxy': self.no_proxy_host})
 
1309
        self.overrideEnv('no_proxy', self.no_proxy_host)
 
1310
        self.overrideEnv('http_proxy', self.proxy_url)
 
1311
        self.assertNotProxied()
1175
1312
 
1176
1313
    def test_HTTP_PROXY_with_NO_PROXY(self):
1177
1314
        if self._testing_pycurl():
1178
1315
            raise tests.TestNotApplicable(
1179
1316
                'pycurl does not check HTTP_PROXY for security reasons')
1180
 
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
1181
 
                                 'NO_PROXY': self.no_proxy_host})
 
1317
        self.overrideEnv('NO_PROXY', self.no_proxy_host)
 
1318
        self.overrideEnv('HTTP_PROXY', self.proxy_url)
 
1319
        self.assertNotProxied()
1182
1320
 
1183
1321
    def test_all_proxy_with_no_proxy(self):
1184
 
        self.not_proxied_in_env({'all_proxy': self.proxy_url,
1185
 
                                 'no_proxy': self.no_proxy_host})
 
1322
        self.overrideEnv('no_proxy', self.no_proxy_host)
 
1323
        self.overrideEnv('all_proxy', self.proxy_url)
 
1324
        self.assertNotProxied()
1186
1325
 
1187
1326
    def test_ALL_PROXY_with_NO_PROXY(self):
1188
 
        self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
1189
 
                                 'NO_PROXY': self.no_proxy_host})
 
1327
        self.overrideEnv('NO_PROXY', self.no_proxy_host)
 
1328
        self.overrideEnv('ALL_PROXY', self.proxy_url)
 
1329
        self.assertNotProxied()
1190
1330
 
1191
1331
    def test_http_proxy_without_scheme(self):
 
1332
        self.overrideEnv('http_proxy', self.server_host_port)
1192
1333
        if self._testing_pycurl():
1193
1334
            # pycurl *ignores* invalid proxy env variables. If that ever change
1194
1335
            # in the future, this test will fail indicating that pycurl do not
1195
1336
            # ignore anymore such variables.
1196
 
            self.not_proxied_in_env({'http_proxy': self.proxy_address})
 
1337
            self.assertNotProxied()
1197
1338
        else:
1198
 
            self.assertRaises(errors.InvalidURL,
1199
 
                              self.proxied_in_env,
1200
 
                              {'http_proxy': self.proxy_address})
 
1339
            self.assertRaises(errors.InvalidURL, self.assertProxied)
1201
1340
 
1202
1341
 
1203
1342
class TestRanges(http_utils.TestCaseWithWebserver):
1204
1343
    """Test the Range header in GET methods."""
1205
1344
 
 
1345
    scenarios = multiply_scenarios(
 
1346
        vary_by_http_client_implementation(),
 
1347
        vary_by_http_protocol_version(),
 
1348
        )
 
1349
 
1206
1350
    def setUp(self):
1207
1351
        http_utils.TestCaseWithWebserver.setUp(self)
1208
1352
        self.build_tree_contents([('a', '0123456789')],)
1209
 
        server = self.get_readonly_server()
1210
 
        self.transport = self._transport(server.get_url())
1211
1353
 
1212
1354
    def create_transport_readonly_server(self):
1213
1355
        return http_server.HttpServer(protocol_version=self._protocol_version)
1214
1356
 
1215
1357
    def _file_contents(self, relpath, ranges):
 
1358
        t = self.get_readonly_transport()
1216
1359
        offsets = [ (start, end - start + 1) for start, end in ranges]
1217
 
        coalesce = self.transport._coalesce_offsets
 
1360
        coalesce = t._coalesce_offsets
1218
1361
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
1219
 
        code, data = self.transport._get(relpath, coalesced)
 
1362
        code, data = t._get(relpath, coalesced)
1220
1363
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1221
1364
        for start, end in ranges:
1222
1365
            data.seek(start)
1223
1366
            yield data.read(end - start + 1)
1224
1367
 
1225
1368
    def _file_tail(self, relpath, tail_amount):
1226
 
        code, data = self.transport._get(relpath, [], tail_amount)
 
1369
        t = self.get_readonly_transport()
 
1370
        code, data = t._get(relpath, [], tail_amount)
1227
1371
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1228
1372
        data.seek(-tail_amount, 2)
1229
1373
        return data.read(tail_amount)
1248
1392
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1249
1393
    """Test redirection between http servers."""
1250
1394
 
1251
 
    def create_transport_secondary_server(self):
1252
 
        """Create the secondary server redirecting to the primary server"""
1253
 
        new = self.get_readonly_server()
1254
 
 
1255
 
        redirecting = http_utils.HTTPServerRedirecting(
1256
 
            protocol_version=self._protocol_version)
1257
 
        redirecting.redirect_to(new.host, new.port)
1258
 
        return redirecting
 
1395
    scenarios = multiply_scenarios(
 
1396
        vary_by_http_client_implementation(),
 
1397
        vary_by_http_protocol_version(),
 
1398
        )
1259
1399
 
1260
1400
    def setUp(self):
1261
1401
        super(TestHTTPRedirections, self).setUp()
1264
1404
                                  '# Bazaar revision bundle v0.9\n#\n')
1265
1405
                                  ],)
1266
1406
 
1267
 
        self.old_transport = self._transport(self.old_server.get_url())
1268
 
 
1269
1407
    def test_redirected(self):
1270
 
        self.assertRaises(errors.RedirectRequested, self.old_transport.get, 'a')
1271
 
        t = self._transport(self.new_server.get_url())
1272
 
        self.assertEqual('0123456789', t.get('a').read())
1273
 
 
1274
 
    def test_read_redirected_bundle_from_url(self):
1275
 
        from bzrlib.bundle import read_bundle_from_url
1276
 
        url = self.old_transport.abspath('bundle')
1277
 
        bundle = read_bundle_from_url(url)
1278
 
        # If read_bundle_from_url was successful we get an empty bundle
1279
 
        self.assertEqual([], bundle.revisions)
 
1408
        self.assertRaises(errors.RedirectRequested,
 
1409
                          self.get_old_transport().get, 'a')
 
1410
        self.assertEqual('0123456789', self.get_new_transport().get('a').read())
1280
1411
 
1281
1412
 
1282
1413
class RedirectedRequest(_urllib2_wrappers.Request):
1291
1422
        # Since the tests using this class will replace
1292
1423
        # _urllib2_wrappers.Request, we can't just call the base class __init__
1293
1424
        # or we'll loop.
1294
 
        RedirectedRequest.init_orig(self, method, url, args, kwargs)
 
1425
        RedirectedRequest.init_orig(self, method, url, *args, **kwargs)
1295
1426
        self.follow_redirections = True
1296
1427
 
1297
1428
 
 
1429
def install_redirected_request(test):
 
1430
    test.overrideAttr(_urllib2_wrappers, 'Request', RedirectedRequest)
 
1431
 
 
1432
 
 
1433
def cleanup_http_redirection_connections(test):
 
1434
    # Some sockets are opened but never seen by _urllib, so we trap them at
 
1435
    # the _urllib2_wrappers level to be able to clean them up.
 
1436
    def socket_disconnect(sock):
 
1437
        try:
 
1438
            sock.shutdown(socket.SHUT_RDWR)
 
1439
            sock.close()
 
1440
        except socket.error:
 
1441
            pass
 
1442
    def connect(connection):
 
1443
        test.http_connect_orig(connection)
 
1444
        test.addCleanup(socket_disconnect, connection.sock)
 
1445
    test.http_connect_orig = test.overrideAttr(
 
1446
        _urllib2_wrappers.HTTPConnection, 'connect', connect)
 
1447
    def connect(connection):
 
1448
        test.https_connect_orig(connection)
 
1449
        test.addCleanup(socket_disconnect, connection.sock)
 
1450
    test.https_connect_orig = test.overrideAttr(
 
1451
        _urllib2_wrappers.HTTPSConnection, 'connect', connect)
 
1452
 
 
1453
 
1298
1454
class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):
1299
1455
    """Test redirections.
1300
1456
 
1309
1465
    -- vila 20070212
1310
1466
    """
1311
1467
 
 
1468
    scenarios = multiply_scenarios(
 
1469
        vary_by_http_client_implementation(),
 
1470
        vary_by_http_protocol_version(),
 
1471
        )
 
1472
 
1312
1473
    def setUp(self):
1313
 
        if pycurl_present and self._transport == PyCurlTransport:
 
1474
        if (features.pycurl.available()
 
1475
            and self._transport == PyCurlTransport):
1314
1476
            raise tests.TestNotApplicable(
1315
 
                "pycurl doesn't redirect silently annymore")
 
1477
                "pycurl doesn't redirect silently anymore")
1316
1478
        super(TestHTTPSilentRedirections, self).setUp()
1317
 
        self.setup_redirected_request()
1318
 
        self.addCleanup(self.cleanup_redirected_request)
 
1479
        install_redirected_request(self)
 
1480
        cleanup_http_redirection_connections(self)
1319
1481
        self.build_tree_contents([('a','a'),
1320
1482
                                  ('1/',),
1321
1483
                                  ('1/a', 'redirected once'),
1329
1491
                                  ('5/a', 'redirected 5 times'),
1330
1492
                                  ],)
1331
1493
 
1332
 
        self.old_transport = self._transport(self.old_server.get_url())
1333
 
 
1334
 
    def setup_redirected_request(self):
1335
 
        self.original_class = _urllib2_wrappers.Request
1336
 
        _urllib2_wrappers.Request = RedirectedRequest
1337
 
 
1338
 
    def cleanup_redirected_request(self):
1339
 
        _urllib2_wrappers.Request = self.original_class
1340
 
 
1341
 
    def create_transport_secondary_server(self):
1342
 
        """Create the secondary server, redirections are defined in the tests"""
1343
 
        return http_utils.HTTPServerRedirecting(
1344
 
            protocol_version=self._protocol_version)
1345
 
 
1346
1494
    def test_one_redirection(self):
1347
 
        t = self.old_transport
1348
 
 
1349
 
        req = RedirectedRequest('GET', t.abspath('a'))
1350
 
        req.follow_redirections = True
 
1495
        t = self.get_old_transport()
 
1496
        req = RedirectedRequest('GET', t._remote_path('a'))
1351
1497
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1352
1498
                                       self.new_server.port)
1353
1499
        self.old_server.redirections = \
1354
1500
            [('(.*)', r'%s/1\1' % (new_prefix), 301),]
1355
 
        self.assertEquals('redirected once',t._perform(req).read())
 
1501
        self.assertEqual('redirected once', t._perform(req).read())
1356
1502
 
1357
1503
    def test_five_redirections(self):
1358
 
        t = self.old_transport
1359
 
 
1360
 
        req = RedirectedRequest('GET', t.abspath('a'))
1361
 
        req.follow_redirections = True
 
1504
        t = self.get_old_transport()
 
1505
        req = RedirectedRequest('GET', t._remote_path('a'))
1362
1506
        old_prefix = 'http://%s:%s' % (self.old_server.host,
1363
1507
                                       self.old_server.port)
1364
1508
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1370
1514
            ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1371
1515
            ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1372
1516
            ]
1373
 
        self.assertEquals('redirected 5 times',t._perform(req).read())
 
1517
        self.assertEqual('redirected 5 times', t._perform(req).read())
1374
1518
 
1375
1519
 
1376
1520
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
1377
1521
    """Test transport.do_catching_redirections."""
1378
1522
 
 
1523
    scenarios = multiply_scenarios(
 
1524
        vary_by_http_client_implementation(),
 
1525
        vary_by_http_protocol_version(),
 
1526
        )
 
1527
 
1379
1528
    def setUp(self):
1380
1529
        super(TestDoCatchRedirections, self).setUp()
1381
1530
        self.build_tree_contents([('a', '0123456789'),],)
1382
 
 
1383
 
        self.old_transport = self._transport(self.old_server.get_url())
1384
 
 
1385
 
    def get_a(self, transport):
1386
 
        return transport.get('a')
 
1531
        cleanup_http_redirection_connections(self)
 
1532
 
 
1533
        self.old_transport = self.get_old_transport()
 
1534
 
 
1535
    def get_a(self, t):
 
1536
        return t.get('a')
1387
1537
 
1388
1538
    def test_no_redirection(self):
1389
 
        t = self._transport(self.new_server.get_url())
 
1539
        t = self.get_new_transport()
1390
1540
 
1391
1541
        # We use None for redirected so that we fail if redirected
1392
 
        self.assertEquals('0123456789',
1393
 
                          transport.do_catching_redirections(
 
1542
        self.assertEqual('0123456789',
 
1543
                         transport.do_catching_redirections(
1394
1544
                self.get_a, t, None).read())
1395
1545
 
1396
1546
    def test_one_redirection(self):
1397
1547
        self.redirections = 0
1398
1548
 
1399
 
        def redirected(transport, exception, redirection_notice):
 
1549
        def redirected(t, exception, redirection_notice):
1400
1550
            self.redirections += 1
1401
 
            dir, file = urlutils.split(exception.target)
1402
 
            return self._transport(dir)
 
1551
            redirected_t = t._redirected_to(exception.source, exception.target)
 
1552
            return redirected_t
1403
1553
 
1404
 
        self.assertEquals('0123456789',
1405
 
                          transport.do_catching_redirections(
 
1554
        self.assertEqual('0123456789',
 
1555
                         transport.do_catching_redirections(
1406
1556
                self.get_a, self.old_transport, redirected).read())
1407
 
        self.assertEquals(1, self.redirections)
 
1557
        self.assertEqual(1, self.redirections)
1408
1558
 
1409
1559
    def test_redirection_loop(self):
1410
1560
 
1419
1569
                          self.get_a, self.old_transport, redirected)
1420
1570
 
1421
1571
 
 
1572
def _setup_authentication_config(**kwargs):
 
1573
    conf = config.AuthenticationConfig()
 
1574
    conf._get_config().update({'httptest': kwargs})
 
1575
    conf._save()
 
1576
 
 
1577
 
 
1578
class TestUrllib2AuthHandler(tests.TestCaseWithTransport):
 
1579
    """Unit tests for glue by which urllib2 asks us for authentication"""
 
1580
 
 
1581
    def test_get_user_password_without_port(self):
 
1582
        """We cope if urllib2 doesn't tell us the port.
 
1583
 
 
1584
        See https://bugs.launchpad.net/bzr/+bug/654684
 
1585
        """
 
1586
        user = 'joe'
 
1587
        password = 'foo'
 
1588
        _setup_authentication_config(scheme='http', host='localhost',
 
1589
                                     user=user, password=password)
 
1590
        handler = _urllib2_wrappers.HTTPAuthHandler()
 
1591
        got_pass = handler.get_user_password(dict(
 
1592
            user='joe',
 
1593
            protocol='http',
 
1594
            host='localhost',
 
1595
            path='/',
 
1596
            realm='Realm',
 
1597
            ))
 
1598
        self.assertEquals((user, password), got_pass)
 
1599
 
 
1600
 
1422
1601
class TestAuth(http_utils.TestCaseWithWebserver):
1423
1602
    """Test authentication scheme"""
1424
1603
 
1425
 
    _auth_header = 'Authorization'
1426
 
    _password_prompt_prefix = ''
 
1604
    scenarios = multiply_scenarios(
 
1605
        vary_by_http_client_implementation(),
 
1606
        vary_by_http_protocol_version(),
 
1607
        vary_by_http_auth_scheme(),
 
1608
        )
1427
1609
 
1428
1610
    def setUp(self):
1429
1611
        super(TestAuth, self).setUp()
1432
1614
                                  ('b', 'contents of b\n'),])
1433
1615
 
1434
1616
    def create_transport_readonly_server(self):
1435
 
        if self._auth_scheme == 'basic':
1436
 
            server = http_utils.HTTPBasicAuthServer(
1437
 
                protocol_version=self._protocol_version)
1438
 
        else:
1439
 
            if self._auth_scheme != 'digest':
1440
 
                raise AssertionError('Unknown auth scheme: %r'
1441
 
                                     % self._auth_scheme)
1442
 
            server = http_utils.HTTPDigestAuthServer(
1443
 
                protocol_version=self._protocol_version)
 
1617
        server = self._auth_server(protocol_version=self._protocol_version)
 
1618
        server._url_protocol = self._url_protocol
1444
1619
        return server
1445
1620
 
1446
1621
    def _testing_pycurl(self):
1447
 
        return pycurl_present and self._transport == PyCurlTransport
 
1622
        # TODO: This is duplicated for lots of the classes in this file
 
1623
        return (features.pycurl.available()
 
1624
                and self._transport == PyCurlTransport)
1448
1625
 
1449
 
    def get_user_url(self, user=None, password=None):
 
1626
    def get_user_url(self, user, password):
1450
1627
        """Build an url embedding user and password"""
1451
1628
        url = '%s://' % self.server._url_protocol
1452
1629
        if user is not None:
1457
1634
        url += '%s:%s/' % (self.server.host, self.server.port)
1458
1635
        return url
1459
1636
 
1460
 
    def get_user_transport(self, user=None, password=None):
1461
 
        return self._transport(self.get_user_url(user, password))
 
1637
    def get_user_transport(self, user, password):
 
1638
        t = transport.get_transport(self.get_user_url(user, password))
 
1639
        return t
1462
1640
 
1463
1641
    def test_no_user(self):
1464
1642
        self.server.add_user('joe', 'foo')
1465
 
        t = self.get_user_transport()
 
1643
        t = self.get_user_transport(None, None)
1466
1644
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1467
1645
        # Only one 'Authentication Required' error should occur
1468
1646
        self.assertEqual(1, self.server.auth_required_errors)
1498
1676
        # initial 'who are you' and 'this is not you, who are you')
1499
1677
        self.assertEqual(2, self.server.auth_required_errors)
1500
1678
 
 
1679
    def test_prompt_for_username(self):
 
1680
        if self._testing_pycurl():
 
1681
            raise tests.TestNotApplicable(
 
1682
                'pycurl cannot prompt, it handles auth by embedding'
 
1683
                ' user:pass in urls only')
 
1684
 
 
1685
        self.server.add_user('joe', 'foo')
 
1686
        t = self.get_user_transport(None, None)
 
1687
        stdout = tests.StringIOWrapper()
 
1688
        stderr = tests.StringIOWrapper()
 
1689
        ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
 
1690
                                            stdout=stdout, stderr=stderr)
 
1691
        self.assertEqual('contents of a\n',t.get('a').read())
 
1692
        # stdin should be empty
 
1693
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
1694
        stderr.seek(0)
 
1695
        expected_prompt = self._expected_username_prompt(t._unqualified_scheme)
 
1696
        self.assertEqual(expected_prompt, stderr.read(len(expected_prompt)))
 
1697
        self.assertEqual('', stdout.getvalue())
 
1698
        self._check_password_prompt(t._unqualified_scheme, 'joe',
 
1699
                                    stderr.readline())
 
1700
 
1501
1701
    def test_prompt_for_password(self):
1502
1702
        if self._testing_pycurl():
1503
1703
            raise tests.TestNotApplicable(
1507
1707
        self.server.add_user('joe', 'foo')
1508
1708
        t = self.get_user_transport('joe', None)
1509
1709
        stdout = tests.StringIOWrapper()
1510
 
        ui.ui_factory = tests.TestUIFactory(stdin='foo\n', stdout=stdout)
1511
 
        self.assertEqual('contents of a\n',t.get('a').read())
 
1710
        stderr = tests.StringIOWrapper()
 
1711
        ui.ui_factory = tests.TestUIFactory(stdin='foo\n',
 
1712
                                            stdout=stdout, stderr=stderr)
 
1713
        self.assertEqual('contents of a\n', t.get('a').read())
1512
1714
        # stdin should be empty
1513
1715
        self.assertEqual('', ui.ui_factory.stdin.readline())
1514
1716
        self._check_password_prompt(t._unqualified_scheme, 'joe',
1515
 
                                    stdout.getvalue())
 
1717
                                    stderr.getvalue())
 
1718
        self.assertEqual('', stdout.getvalue())
1516
1719
        # And we shouldn't prompt again for a different request
1517
1720
        # against the same transport.
1518
1721
        self.assertEqual('contents of b\n',t.get('b').read())
1528
1731
                              % (scheme.upper(),
1529
1732
                                 user, self.server.host, self.server.port,
1530
1733
                                 self.server.auth_realm)))
1531
 
        self.assertEquals(expected_prompt, actual_prompt)
 
1734
        self.assertEqual(expected_prompt, actual_prompt)
 
1735
 
 
1736
    def _expected_username_prompt(self, scheme):
 
1737
        return (self._username_prompt_prefix
 
1738
                + "%s %s:%d, Realm: '%s' username: " % (scheme.upper(),
 
1739
                                 self.server.host, self.server.port,
 
1740
                                 self.server.auth_realm))
1532
1741
 
1533
1742
    def test_no_prompt_for_password_when_using_auth_config(self):
1534
1743
        if self._testing_pycurl():
1542
1751
        self.server.add_user(user, password)
1543
1752
        t = self.get_user_transport(user, None)
1544
1753
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1545
 
                                            stdout=tests.StringIOWrapper())
 
1754
                                            stderr=tests.StringIOWrapper())
1546
1755
        # Create a minimal config file with the right password
1547
 
        conf = config.AuthenticationConfig()
1548
 
        conf._get_config().update(
1549
 
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1550
 
                          'user': user, 'password': password}})
1551
 
        conf._save()
 
1756
        _setup_authentication_config(scheme='http', port=self.server.port,
 
1757
                                     user=user, password=password)
1552
1758
        # Issue a request to the server to connect
1553
1759
        self.assertEqual('contents of a\n',t.get('a').read())
1554
1760
        # stdin should have  been left untouched
1557
1763
        self.assertEqual(1, self.server.auth_required_errors)
1558
1764
 
1559
1765
    def test_changing_nonce(self):
1560
 
        if self._auth_scheme != 'digest':
1561
 
            raise tests.TestNotApplicable('HTTP auth digest only test')
 
1766
        if self._auth_server not in (http_utils.HTTPDigestAuthServer,
 
1767
                                     http_utils.ProxyDigestAuthServer):
 
1768
            raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
1562
1769
        if self._testing_pycurl():
1563
 
            raise tests.KnownFailure(
 
1770
            self.knownFailure(
1564
1771
                'pycurl does not handle a nonce change')
1565
1772
        self.server.add_user('joe', 'foo')
1566
1773
        t = self.get_user_transport('joe', 'foo')
1576
1783
        # initial 'who are you' and a second 'who are you' with the new nonce)
1577
1784
        self.assertEqual(2, self.server.auth_required_errors)
1578
1785
 
 
1786
    def test_user_from_auth_conf(self):
 
1787
        if self._testing_pycurl():
 
1788
            raise tests.TestNotApplicable(
 
1789
                'pycurl does not support authentication.conf')
 
1790
        user = 'joe'
 
1791
        password = 'foo'
 
1792
        self.server.add_user(user, password)
 
1793
        _setup_authentication_config(scheme='http', port=self.server.port,
 
1794
                                     user=user, password=password)
 
1795
        t = self.get_user_transport(None, None)
 
1796
        # Issue a request to the server to connect
 
1797
        self.assertEqual('contents of a\n', t.get('a').read())
 
1798
        # Only one 'Authentication Required' error should occur
 
1799
        self.assertEqual(1, self.server.auth_required_errors)
 
1800
 
 
1801
    def test_no_credential_leaks_in_log(self):
 
1802
        self.overrideAttr(debug, 'debug_flags', set(['http']))
 
1803
        user = 'joe'
 
1804
        password = 'very-sensitive-password'
 
1805
        self.server.add_user(user, password)
 
1806
        t = self.get_user_transport(user, password)
 
1807
        # Capture the debug calls to mutter
 
1808
        self.mutters = []
 
1809
        def mutter(*args):
 
1810
            lines = args[0] % args[1:]
 
1811
            # Some calls output multiple lines, just split them now since we
 
1812
            # care about a single one later.
 
1813
            self.mutters.extend(lines.splitlines())
 
1814
        self.overrideAttr(trace, 'mutter', mutter)
 
1815
        # Issue a request to the server to connect
 
1816
        self.assertEqual(True, t.has('a'))
 
1817
        # Only one 'Authentication Required' error should occur
 
1818
        self.assertEqual(1, self.server.auth_required_errors)
 
1819
        # Since the authentification succeeded, there should be a corresponding
 
1820
        # debug line
 
1821
        sent_auth_headers = [line for line in self.mutters
 
1822
                             if line.startswith('> %s' % (self._auth_header,))]
 
1823
        self.assertLength(1, sent_auth_headers)
 
1824
        self.assertStartsWith(sent_auth_headers[0],
 
1825
                              '> %s: <masked>' % (self._auth_header,))
1579
1826
 
1580
1827
 
1581
1828
class TestProxyAuth(TestAuth):
1582
 
    """Test proxy authentication schemes."""
1583
 
 
1584
 
    _auth_header = 'Proxy-authorization'
1585
 
    _password_prompt_prefix='Proxy '
 
1829
    """Test proxy authentication schemes.
 
1830
 
 
1831
    This inherits from TestAuth to tweak the setUp and filter some failing
 
1832
    tests.
 
1833
    """
 
1834
 
 
1835
    scenarios = multiply_scenarios(
 
1836
        vary_by_http_client_implementation(),
 
1837
        vary_by_http_protocol_version(),
 
1838
        vary_by_http_proxy_auth_scheme(),
 
1839
        )
1586
1840
 
1587
1841
    def setUp(self):
1588
1842
        super(TestProxyAuth, self).setUp()
1589
 
        self._old_env = {}
1590
 
        self.addCleanup(self._restore_env)
1591
1843
        # Override the contents to avoid false positives
1592
1844
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
1593
1845
                                  ('b', 'not proxied contents of b\n'),
1595
1847
                                  ('b-proxied', 'contents of b\n'),
1596
1848
                                  ])
1597
1849
 
1598
 
    def create_transport_readonly_server(self):
1599
 
        if self._auth_scheme == 'basic':
1600
 
            server = http_utils.ProxyBasicAuthServer(
1601
 
                protocol_version=self._protocol_version)
1602
 
        else:
1603
 
            if self._auth_scheme != 'digest':
1604
 
                raise AssertionError('Unknown auth scheme: %r'
1605
 
                                     % self._auth_scheme)
1606
 
            server = http_utils.ProxyDigestAuthServer(
1607
 
                protocol_version=self._protocol_version)
1608
 
        return server
1609
 
 
1610
 
    def get_user_transport(self, user=None, password=None):
1611
 
        self._install_env({'all_proxy': self.get_user_url(user, password)})
1612
 
        return self._transport(self.server.get_url())
1613
 
 
1614
 
    def _install_env(self, env):
1615
 
        for name, value in env.iteritems():
1616
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1617
 
 
1618
 
    def _restore_env(self):
1619
 
        for name, value in self._old_env.iteritems():
1620
 
            osutils.set_or_unset_env(name, value)
 
1850
    def get_user_transport(self, user, password):
 
1851
        self.overrideEnv('all_proxy', self.get_user_url(user, password))
 
1852
        return TestAuth.get_user_transport(self, user, password)
1621
1853
 
1622
1854
    def test_empty_pass(self):
1623
1855
        if self._testing_pycurl():
1624
1856
            import pycurl
1625
1857
            if pycurl.version_info()[1] < '7.16.0':
1626
 
                raise tests.KnownFailure(
 
1858
                self.knownFailure(
1627
1859
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
1628
1860
        super(TestProxyAuth, self).test_empty_pass()
1629
1861
 
1642
1874
        self.readfile = StringIO(socket_read_content)
1643
1875
        self.writefile = StringIO()
1644
1876
        self.writefile.close = lambda: None
 
1877
        self.close = lambda: None
1645
1878
 
1646
1879
    def makefile(self, mode='r', bufsize=None):
1647
1880
        if 'r' in mode:
1652
1885
 
1653
1886
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1654
1887
 
 
1888
    scenarios = multiply_scenarios(
 
1889
        vary_by_http_client_implementation(),
 
1890
        vary_by_http_protocol_version(),
 
1891
        )
 
1892
 
1655
1893
    def setUp(self):
1656
1894
        super(SmartHTTPTunnellingTest, self).setUp()
1657
1895
        # We use the VFS layer as part of HTTP tunnelling tests.
1658
 
        self._captureVar('BZR_NO_SMART_VFS', None)
 
1896
        self.overrideEnv('BZR_NO_SMART_VFS', None)
1659
1897
        self.transport_readonly_server = http_utils.HTTPServerWithSmarts
 
1898
        self.http_server = self.get_readonly_server()
1660
1899
 
1661
1900
    def create_transport_readonly_server(self):
1662
 
        return http_utils.HTTPServerWithSmarts(
 
1901
        server = http_utils.HTTPServerWithSmarts(
1663
1902
            protocol_version=self._protocol_version)
 
1903
        server._url_protocol = self._url_protocol
 
1904
        return server
1664
1905
 
1665
1906
    def test_open_bzrdir(self):
1666
1907
        branch = self.make_branch('relpath')
1667
 
        http_server = self.get_readonly_server()
1668
 
        url = http_server.get_url() + 'relpath'
 
1908
        url = self.http_server.get_url() + 'relpath'
1669
1909
        bd = bzrdir.BzrDir.open(url)
 
1910
        self.addCleanup(bd.transport.disconnect)
1670
1911
        self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
1671
1912
 
1672
1913
    def test_bulk_data(self):
1674
1915
        # The 'readv' command in the smart protocol both sends and receives
1675
1916
        # bulk data, so we use that.
1676
1917
        self.build_tree(['data-file'])
1677
 
        http_server = self.get_readonly_server()
1678
 
        http_transport = self._transport(http_server.get_url())
 
1918
        http_transport = transport.get_transport(self.http_server.get_url())
1679
1919
        medium = http_transport.get_smart_medium()
1680
1920
        # Since we provide the medium, the url below will be mostly ignored
1681
1921
        # during the test, as long as the path is '/'.
1689
1929
        post_body = 'hello\n'
1690
1930
        expected_reply_body = 'ok\x012\n'
1691
1931
 
1692
 
        http_server = self.get_readonly_server()
1693
 
        http_transport = self._transport(http_server.get_url())
 
1932
        http_transport = transport.get_transport(self.http_server.get_url())
1694
1933
        medium = http_transport.get_smart_medium()
1695
1934
        response = medium.send_http_smart_request(post_body)
1696
1935
        reply_body = response.read()
1697
1936
        self.assertEqual(expected_reply_body, reply_body)
1698
1937
 
1699
1938
    def test_smart_http_server_post_request_handler(self):
1700
 
        httpd = self.get_readonly_server()._get_httpd()
 
1939
        httpd = self.http_server.server
1701
1940
 
1702
1941
        socket = SampleSocket(
1703
1942
            'POST /.bzr/smart %s \r\n' % self._protocol_version
1735
1974
 
1736
1975
    def test_probe_smart_server(self):
1737
1976
        """Test error handling against server refusing smart requests."""
1738
 
        server = self.get_readonly_server()
1739
 
        t = self._transport(server.get_url())
 
1977
        t = self.get_readonly_transport()
1740
1978
        # No need to build a valid smart request here, the server will not even
1741
1979
        # try to interpret it.
1742
1980
        self.assertRaises(errors.SmartProtocolError,
1743
1981
                          t.get_smart_medium().send_http_smart_request,
1744
1982
                          'whatever')
1745
1983
 
 
1984
 
 
1985
class Test_redirected_to(tests.TestCase):
 
1986
 
 
1987
    scenarios = vary_by_http_client_implementation()
 
1988
 
 
1989
    def test_redirected_to_subdir(self):
 
1990
        t = self._transport('http://www.example.com/foo')
 
1991
        r = t._redirected_to('http://www.example.com/foo',
 
1992
                             'http://www.example.com/foo/subdir')
 
1993
        self.assertIsInstance(r, type(t))
 
1994
        # Both transports share the some connection
 
1995
        self.assertEqual(t._get_connection(), r._get_connection())
 
1996
 
 
1997
    def test_redirected_to_self_with_slash(self):
 
1998
        t = self._transport('http://www.example.com/foo')
 
1999
        r = t._redirected_to('http://www.example.com/foo',
 
2000
                             'http://www.example.com/foo/')
 
2001
        self.assertIsInstance(r, type(t))
 
2002
        # Both transports share the some connection (one can argue that we
 
2003
        # should return the exact same transport here, but that seems
 
2004
        # overkill).
 
2005
        self.assertEqual(t._get_connection(), r._get_connection())
 
2006
 
 
2007
    def test_redirected_to_host(self):
 
2008
        t = self._transport('http://www.example.com/foo')
 
2009
        r = t._redirected_to('http://www.example.com/foo',
 
2010
                             'http://foo.example.com/foo/subdir')
 
2011
        self.assertIsInstance(r, type(t))
 
2012
 
 
2013
    def test_redirected_to_same_host_sibling_protocol(self):
 
2014
        t = self._transport('http://www.example.com/foo')
 
2015
        r = t._redirected_to('http://www.example.com/foo',
 
2016
                             'https://www.example.com/foo')
 
2017
        self.assertIsInstance(r, type(t))
 
2018
 
 
2019
    def test_redirected_to_same_host_different_protocol(self):
 
2020
        t = self._transport('http://www.example.com/foo')
 
2021
        r = t._redirected_to('http://www.example.com/foo',
 
2022
                             'ftp://www.example.com/foo')
 
2023
        self.assertNotEquals(type(r), type(t))
 
2024
 
 
2025
    def test_redirected_to_different_host_same_user(self):
 
2026
        t = self._transport('http://joe@www.example.com/foo')
 
2027
        r = t._redirected_to('http://www.example.com/foo',
 
2028
                             'https://foo.example.com/foo')
 
2029
        self.assertIsInstance(r, type(t))
 
2030
        self.assertEqual(t._parsed_url.user, r._parsed_url.user)
 
2031
 
 
2032
 
 
2033
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
 
2034
    """Request handler for a unique and pre-defined request.
 
2035
 
 
2036
    The only thing we care about here is how many bytes travel on the wire. But
 
2037
    since we want to measure it for a real http client, we have to send it
 
2038
    correct responses.
 
2039
 
 
2040
    We expect to receive a *single* request nothing more (and we won't even
 
2041
    check what request it is, we just measure the bytes read until an empty
 
2042
    line.
 
2043
    """
 
2044
 
 
2045
    def _handle_one_request(self):
 
2046
        tcs = self.server.test_case_server
 
2047
        requestline = self.rfile.readline()
 
2048
        headers = self.MessageClass(self.rfile, 0)
 
2049
        # We just read: the request, the headers, an empty line indicating the
 
2050
        # end of the headers.
 
2051
        bytes_read = len(requestline)
 
2052
        for line in headers.headers:
 
2053
            bytes_read += len(line)
 
2054
        bytes_read += len('\r\n')
 
2055
        if requestline.startswith('POST'):
 
2056
            # The body should be a single line (or we don't know where it ends
 
2057
            # and we don't want to issue a blocking read)
 
2058
            body = self.rfile.readline()
 
2059
            bytes_read += len(body)
 
2060
        tcs.bytes_read = bytes_read
 
2061
 
 
2062
        # We set the bytes written *before* issuing the write, the client is
 
2063
        # supposed to consume every produced byte *before* checking that value.
 
2064
 
 
2065
        # Doing the oppposite may lead to test failure: we may be interrupted
 
2066
        # after the write but before updating the value. The client can then
 
2067
        # continue and read the value *before* we can update it. And yes,
 
2068
        # this has been observed -- vila 20090129
 
2069
        tcs.bytes_written = len(tcs.canned_response)
 
2070
        self.wfile.write(tcs.canned_response)
 
2071
 
 
2072
 
 
2073
class ActivityServerMixin(object):
 
2074
 
 
2075
    def __init__(self, protocol_version):
 
2076
        super(ActivityServerMixin, self).__init__(
 
2077
            request_handler=PredefinedRequestHandler,
 
2078
            protocol_version=protocol_version)
 
2079
        # Bytes read and written by the server
 
2080
        self.bytes_read = 0
 
2081
        self.bytes_written = 0
 
2082
        self.canned_response = None
 
2083
 
 
2084
 
 
2085
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
 
2086
    pass
 
2087
 
 
2088
 
 
2089
if features.HTTPSServerFeature.available():
 
2090
    from bzrlib.tests import https_server
 
2091
    class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
 
2092
        pass
 
2093
 
 
2094
 
 
2095
class TestActivityMixin(object):
 
2096
    """Test socket activity reporting.
 
2097
 
 
2098
    We use a special purpose server to control the bytes sent and received and
 
2099
    be able to predict the activity on the client socket.
 
2100
    """
 
2101
 
 
2102
    def setUp(self):
 
2103
        tests.TestCase.setUp(self)
 
2104
        self.server = self._activity_server(self._protocol_version)
 
2105
        self.server.start_server()
 
2106
        _activities = {} # Don't close over self and create a cycle
 
2107
        def report_activity(t, bytes, direction):
 
2108
            count = _activities.get(direction, 0)
 
2109
            count += bytes
 
2110
            _activities[direction] = count
 
2111
        self.activities = _activities
 
2112
 
 
2113
        # We override at class level because constructors may propagate the
 
2114
        # bound method and render instance overriding ineffective (an
 
2115
        # alternative would be to define a specific ui factory instead...)
 
2116
        self.overrideAttr(self._transport, '_report_activity', report_activity)
 
2117
        self.addCleanup(self.server.stop_server)
 
2118
 
 
2119
    def get_transport(self):
 
2120
        t = self._transport(self.server.get_url())
 
2121
        # FIXME: Needs cleanup -- vila 20100611
 
2122
        return t
 
2123
 
 
2124
    def assertActivitiesMatch(self):
 
2125
        self.assertEqual(self.server.bytes_read,
 
2126
                         self.activities.get('write', 0), 'written bytes')
 
2127
        self.assertEqual(self.server.bytes_written,
 
2128
                         self.activities.get('read', 0), 'read bytes')
 
2129
 
 
2130
    def test_get(self):
 
2131
        self.server.canned_response = '''HTTP/1.1 200 OK\r
 
2132
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
 
2133
Server: Apache/2.0.54 (Fedora)\r
 
2134
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
 
2135
ETag: "56691-23-38e9ae00"\r
 
2136
Accept-Ranges: bytes\r
 
2137
Content-Length: 35\r
 
2138
Connection: close\r
 
2139
Content-Type: text/plain; charset=UTF-8\r
 
2140
\r
 
2141
Bazaar-NG meta directory, format 1
 
2142
'''
 
2143
        t = self.get_transport()
 
2144
        self.assertEqual('Bazaar-NG meta directory, format 1\n',
 
2145
                         t.get('foo/bar').read())
 
2146
        self.assertActivitiesMatch()
 
2147
 
 
2148
    def test_has(self):
 
2149
        self.server.canned_response = '''HTTP/1.1 200 OK\r
 
2150
Server: SimpleHTTP/0.6 Python/2.5.2\r
 
2151
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
 
2152
Content-type: application/octet-stream\r
 
2153
Content-Length: 20\r
 
2154
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
 
2155
\r
 
2156
'''
 
2157
        t = self.get_transport()
 
2158
        self.assertTrue(t.has('foo/bar'))
 
2159
        self.assertActivitiesMatch()
 
2160
 
 
2161
    def test_readv(self):
 
2162
        self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
 
2163
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
 
2164
Server: Apache/2.0.54 (Fedora)\r
 
2165
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
 
2166
ETag: "238a3c-16ec2-805c5540"\r
 
2167
Accept-Ranges: bytes\r
 
2168
Content-Length: 1534\r
 
2169
Connection: close\r
 
2170
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
 
2171
\r
 
2172
\r
 
2173
--418470f848b63279b\r
 
2174
Content-type: text/plain; charset=UTF-8\r
 
2175
Content-range: bytes 0-254/93890\r
 
2176
\r
 
2177
mbp@sourcefrog.net-20050309040815-13242001617e4a06
 
2178
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
 
2179
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
 
2180
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
 
2181
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
 
2182
\r
 
2183
--418470f848b63279b\r
 
2184
Content-type: text/plain; charset=UTF-8\r
 
2185
Content-range: bytes 1000-2049/93890\r
 
2186
\r
 
2187
40-fd4ec249b6b139ab
 
2188
mbp@sourcefrog.net-20050311063625-07858525021f270b
 
2189
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
 
2190
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
 
2191
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
 
2192
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
 
2193
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
 
2194
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
 
2195
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
 
2196
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
 
2197
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
 
2198
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
 
2199
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
 
2200
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
 
2201
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
 
2202
mbp@sourcefrog.net-20050313120651-497bd231b19df600
 
2203
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
 
2204
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
 
2205
mbp@sourcefrog.net-20050314025539-637a636692c055cf
 
2206
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
 
2207
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
 
2208
mbp@source\r
 
2209
--418470f848b63279b--\r
 
2210
'''
 
2211
        t = self.get_transport()
 
2212
        # Remember that the request is ignored and that the ranges below
 
2213
        # doesn't have to match the canned response.
 
2214
        l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
 
2215
        self.assertEqual(2, len(l))
 
2216
        self.assertActivitiesMatch()
 
2217
 
 
2218
    def test_post(self):
 
2219
        self.server.canned_response = '''HTTP/1.1 200 OK\r
 
2220
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
 
2221
Server: Apache/2.0.54 (Fedora)\r
 
2222
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
 
2223
ETag: "56691-23-38e9ae00"\r
 
2224
Accept-Ranges: bytes\r
 
2225
Content-Length: 35\r
 
2226
Connection: close\r
 
2227
Content-Type: text/plain; charset=UTF-8\r
 
2228
\r
 
2229
lalala whatever as long as itsssss
 
2230
'''
 
2231
        t = self.get_transport()
 
2232
        # We must send a single line of body bytes, see
 
2233
        # PredefinedRequestHandler._handle_one_request
 
2234
        code, f = t._post('abc def end-of-body\n')
 
2235
        self.assertEqual('lalala whatever as long as itsssss\n', f.read())
 
2236
        self.assertActivitiesMatch()
 
2237
 
 
2238
 
 
2239
class TestActivity(tests.TestCase, TestActivityMixin):
 
2240
 
 
2241
    scenarios = multiply_scenarios(
 
2242
        vary_by_http_activity(),
 
2243
        vary_by_http_protocol_version(),
 
2244
        )
 
2245
 
 
2246
    def setUp(self):
 
2247
        TestActivityMixin.setUp(self)
 
2248
 
 
2249
 
 
2250
class TestNoReportActivity(tests.TestCase, TestActivityMixin):
 
2251
 
 
2252
    # Unlike TestActivity, we are really testing ReportingFileSocket and
 
2253
    # ReportingSocket, so we don't need all the parametrization. Since
 
2254
    # ReportingFileSocket and ReportingSocket are wrappers, it's easier to
 
2255
    # test them through their use by the transport than directly (that's a
 
2256
    # bit less clean but far more simpler and effective).
 
2257
    _activity_server = ActivityHTTPServer
 
2258
    _protocol_version = 'HTTP/1.1'
 
2259
 
 
2260
    def setUp(self):
 
2261
        self._transport =_urllib.HttpTransport_urllib
 
2262
        TestActivityMixin.setUp(self)
 
2263
 
 
2264
    def assertActivitiesMatch(self):
 
2265
        # Nothing to check here
 
2266
        pass
 
2267
 
 
2268
 
 
2269
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
 
2270
    """Test authentication on the redirected http server."""
 
2271
 
 
2272
    scenarios = vary_by_http_protocol_version()
 
2273
 
 
2274
    _auth_header = 'Authorization'
 
2275
    _password_prompt_prefix = ''
 
2276
    _username_prompt_prefix = ''
 
2277
    _auth_server = http_utils.HTTPBasicAuthServer
 
2278
    _transport = _urllib.HttpTransport_urllib
 
2279
 
 
2280
    def setUp(self):
 
2281
        super(TestAuthOnRedirected, self).setUp()
 
2282
        self.build_tree_contents([('a','a'),
 
2283
                                  ('1/',),
 
2284
                                  ('1/a', 'redirected once'),
 
2285
                                  ],)
 
2286
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
2287
                                       self.new_server.port)
 
2288
        self.old_server.redirections = [
 
2289
            ('(.*)', r'%s/1\1' % (new_prefix), 301),]
 
2290
        self.old_transport = self.get_old_transport()
 
2291
        self.new_server.add_user('joe', 'foo')
 
2292
        cleanup_http_redirection_connections(self)
 
2293
 
 
2294
    def create_transport_readonly_server(self):
 
2295
        server = self._auth_server(protocol_version=self._protocol_version)
 
2296
        server._url_protocol = self._url_protocol
 
2297
        return server
 
2298
 
 
2299
    def get_a(self, t):
 
2300
        return t.get('a')
 
2301
 
 
2302
    def test_auth_on_redirected_via_do_catching_redirections(self):
 
2303
        self.redirections = 0
 
2304
 
 
2305
        def redirected(t, exception, redirection_notice):
 
2306
            self.redirections += 1
 
2307
            redirected_t = t._redirected_to(exception.source, exception.target)
 
2308
            self.addCleanup(redirected_t.disconnect)
 
2309
            return redirected_t
 
2310
 
 
2311
        stdout = tests.StringIOWrapper()
 
2312
        stderr = tests.StringIOWrapper()
 
2313
        ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
 
2314
                                            stdout=stdout, stderr=stderr)
 
2315
        self.assertEqual('redirected once',
 
2316
                         transport.do_catching_redirections(
 
2317
                self.get_a, self.old_transport, redirected).read())
 
2318
        self.assertEqual(1, self.redirections)
 
2319
        # stdin should be empty
 
2320
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
2321
        # stdout should be empty, stderr will contains the prompts
 
2322
        self.assertEqual('', stdout.getvalue())
 
2323
 
 
2324
    def test_auth_on_redirected_via_following_redirections(self):
 
2325
        self.new_server.add_user('joe', 'foo')
 
2326
        stdout = tests.StringIOWrapper()
 
2327
        stderr = tests.StringIOWrapper()
 
2328
        ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
 
2329
                                            stdout=stdout, stderr=stderr)
 
2330
        t = self.old_transport
 
2331
        req = RedirectedRequest('GET', t.abspath('a'))
 
2332
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
2333
                                       self.new_server.port)
 
2334
        self.old_server.redirections = [
 
2335
            ('(.*)', r'%s/1\1' % (new_prefix), 301),]
 
2336
        self.assertEqual('redirected once', t._perform(req).read())
 
2337
        # stdin should be empty
 
2338
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
2339
        # stdout should be empty, stderr will contains the prompts
 
2340
        self.assertEqual('', stdout.getvalue())
 
2341