~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Andrew Bennetts
  • Date: 2010-10-13 00:26:41 UTC
  • mto: This revision was merged to the branch mainline in revision 5498.
  • Revision ID: andrew.bennetts@canonical.com-20101013002641-9tlh9k89mlj1666m
Keep docs-plain working.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2010 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Tests for HTTP implementations.
 
18
 
 
19
This module defines a load_tests() method that parametrize tests classes for
 
20
transport implementation, http protocol versions and authentication schemes.
 
21
"""
 
22
 
 
23
# TODO: Should be renamed to bzrlib.transport.http.tests?
 
24
# TODO: What about renaming to bzrlib.tests.transport.http ?
 
25
 
 
26
from cStringIO import StringIO
 
27
import httplib
 
28
import os
 
29
import select
 
30
import SimpleHTTPServer
 
31
import socket
 
32
import sys
 
33
import threading
 
34
 
 
35
import bzrlib
 
36
from bzrlib import (
 
37
    bzrdir,
 
38
    config,
 
39
    errors,
 
40
    osutils,
 
41
    remote as _mod_remote,
 
42
    tests,
 
43
    transport,
 
44
    ui,
 
45
    urlutils,
 
46
    )
 
47
from bzrlib.tests import (
 
48
    features,
 
49
    http_server,
 
50
    http_utils,
 
51
    test_server,
 
52
    )
 
53
from bzrlib.transport import (
 
54
    http,
 
55
    remote,
 
56
    )
 
57
from bzrlib.transport.http import (
 
58
    _urllib,
 
59
    _urllib2_wrappers,
 
60
    )
 
61
 
 
62
 
 
63
if features.pycurl.available():
 
64
    from bzrlib.transport.http._pycurl import PyCurlTransport
 
65
 
 
66
 
 
67
def load_tests(standard_tests, module, loader):
 
68
    """Multiply tests for http clients and protocol versions."""
 
69
    result = loader.suiteClass()
 
70
 
 
71
    # one for each transport implementation
 
72
    t_tests, remaining_tests = tests.split_suite_by_condition(
 
73
        standard_tests, tests.condition_isinstance((
 
74
                TestHttpTransportRegistration,
 
75
                TestHttpTransportUrls,
 
76
                Test_redirected_to,
 
77
                )))
 
78
    transport_scenarios = [
 
79
        ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
 
80
                        _server=http_server.HttpServer_urllib,
 
81
                        _url_protocol='http+urllib',)),
 
82
        ]
 
83
    if features.pycurl.available():
 
84
        transport_scenarios.append(
 
85
            ('pycurl', dict(_transport=PyCurlTransport,
 
86
                            _server=http_server.HttpServer_PyCurl,
 
87
                            _url_protocol='http+pycurl',)))
 
88
    tests.multiply_tests(t_tests, transport_scenarios, result)
 
89
 
 
90
    protocol_scenarios = [
 
91
            ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
 
92
            ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
 
93
            ]
 
94
 
 
95
    # some tests are parametrized by the protocol version only
 
96
    p_tests, remaining_tests = tests.split_suite_by_condition(
 
97
        remaining_tests, tests.condition_isinstance((
 
98
                TestAuthOnRedirected,
 
99
                )))
 
100
    tests.multiply_tests(p_tests, protocol_scenarios, result)
 
101
 
 
102
    # each implementation tested with each HTTP version
 
103
    tp_tests, remaining_tests = tests.split_suite_by_condition(
 
104
        remaining_tests, tests.condition_isinstance((
 
105
                SmartHTTPTunnellingTest,
 
106
                TestDoCatchRedirections,
 
107
                TestHTTPConnections,
 
108
                TestHTTPRedirections,
 
109
                TestHTTPSilentRedirections,
 
110
                TestLimitedRangeRequestServer,
 
111
                TestPost,
 
112
                TestProxyHttpServer,
 
113
                TestRanges,
 
114
                TestSpecificRequestHandler,
 
115
                )))
 
116
    tp_scenarios = tests.multiply_scenarios(transport_scenarios,
 
117
                                            protocol_scenarios)
 
118
    tests.multiply_tests(tp_tests, tp_scenarios, result)
 
119
 
 
120
    # proxy auth: each auth scheme on all http versions on all implementations.
 
121
    tppa_tests, remaining_tests = tests.split_suite_by_condition(
 
122
        remaining_tests, tests.condition_isinstance((
 
123
                TestProxyAuth,
 
124
                )))
 
125
    proxy_auth_scheme_scenarios = [
 
126
        ('basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
 
127
        ('digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
 
128
        ('basicdigest',
 
129
         dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
130
        ]
 
131
    tppa_scenarios = tests.multiply_scenarios(tp_scenarios,
 
132
                                              proxy_auth_scheme_scenarios)
 
133
    tests.multiply_tests(tppa_tests, tppa_scenarios, result)
 
134
 
 
135
    # auth: each auth scheme on all http versions on all implementations.
 
136
    tpa_tests, remaining_tests = tests.split_suite_by_condition(
 
137
        remaining_tests, tests.condition_isinstance((
 
138
                TestAuth,
 
139
                )))
 
140
    auth_scheme_scenarios = [
 
141
        ('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
 
142
        ('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
 
143
        ('basicdigest',
 
144
         dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
 
145
        ]
 
146
    tpa_scenarios = tests.multiply_scenarios(tp_scenarios,
 
147
                                             auth_scheme_scenarios)
 
148
    tests.multiply_tests(tpa_tests, tpa_scenarios, result)
 
149
 
 
150
    # activity: on all http[s] versions on all implementations
 
151
    tpact_tests, remaining_tests = tests.split_suite_by_condition(
 
152
        remaining_tests, tests.condition_isinstance((
 
153
                TestActivity,
 
154
                )))
 
155
    activity_scenarios = [
 
156
        ('urllib,http', dict(_activity_server=ActivityHTTPServer,
 
157
                             _transport=_urllib.HttpTransport_urllib,)),
 
158
        ]
 
159
    if tests.HTTPSServerFeature.available():
 
160
        activity_scenarios.append(
 
161
            ('urllib,https', dict(_activity_server=ActivityHTTPSServer,
 
162
                                  _transport=_urllib.HttpTransport_urllib,)),)
 
163
    if features.pycurl.available():
 
164
        activity_scenarios.append(
 
165
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
 
166
                                 _transport=PyCurlTransport,)),)
 
167
        if tests.HTTPSServerFeature.available():
 
168
            from bzrlib.tests import (
 
169
                ssl_certs,
 
170
                )
 
171
            # FIXME: Until we have a better way to handle self-signed
 
172
            # certificates (like allowing them in a test specific
 
173
            # authentication.conf for example), we need some specialized pycurl
 
174
            # transport for tests.
 
175
            class HTTPS_pycurl_transport(PyCurlTransport):
 
176
 
 
177
                def __init__(self, base, _from_transport=None):
 
178
                    super(HTTPS_pycurl_transport, self).__init__(
 
179
                        base, _from_transport)
 
180
                    self.cabundle = str(ssl_certs.build_path('ca.crt'))
 
181
 
 
182
            activity_scenarios.append(
 
183
                ('pycurl,https', dict(_activity_server=ActivityHTTPSServer,
 
184
                                      _transport=HTTPS_pycurl_transport,)),)
 
185
 
 
186
    tpact_scenarios = tests.multiply_scenarios(activity_scenarios,
 
187
                                               protocol_scenarios)
 
188
    tests.multiply_tests(tpact_tests, tpact_scenarios, result)
 
189
 
 
190
    # No parametrization for the remaining tests
 
191
    result.addTests(remaining_tests)
 
192
 
 
193
    return result
 
194
 
 
195
 
 
196
class FakeManager(object):
 
197
 
 
198
    def __init__(self):
 
199
        self.credentials = []
 
200
 
 
201
    def add_password(self, realm, host, username, password):
 
202
        self.credentials.append([realm, host, username, password])
 
203
 
 
204
 
 
205
class RecordingServer(object):
 
206
    """A fake HTTP server.
 
207
 
 
208
    It records the bytes sent to it, and replies with a 200.
 
209
    """
 
210
 
 
211
    def __init__(self, expect_body_tail=None, scheme=''):
 
212
        """Constructor.
 
213
 
 
214
        :type expect_body_tail: str
 
215
        :param expect_body_tail: a reply won't be sent until this string is
 
216
            received.
 
217
        """
 
218
        self._expect_body_tail = expect_body_tail
 
219
        self.host = None
 
220
        self.port = None
 
221
        self.received_bytes = ''
 
222
        self.scheme = scheme
 
223
 
 
224
    def get_url(self):
 
225
        return '%s://%s:%s/' % (self.scheme, self.host, self.port)
 
226
 
 
227
    def start_server(self):
 
228
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
229
        self._sock.bind(('127.0.0.1', 0))
 
230
        self.host, self.port = self._sock.getsockname()
 
231
        self._ready = threading.Event()
 
232
        self._thread = test_server.ThreadWithException(
 
233
            event=self._ready, target=self._accept_read_and_reply)
 
234
        self._thread.start()
 
235
        if 'threads' in tests.selftest_debug_flags:
 
236
            sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
 
237
        self._ready.wait()
 
238
 
 
239
    def _accept_read_and_reply(self):
 
240
        self._sock.listen(1)
 
241
        self._ready.set()
 
242
        conn, address = self._sock.accept()
 
243
        if self._expect_body_tail is not None:
 
244
            while not self.received_bytes.endswith(self._expect_body_tail):
 
245
                self.received_bytes += conn.recv(4096)
 
246
            conn.sendall('HTTP/1.1 200 OK\r\n')
 
247
        try:
 
248
            self._sock.close()
 
249
        except socket.error:
 
250
            # The client may have already closed the socket.
 
251
            pass
 
252
 
 
253
    def stop_server(self):
 
254
        try:
 
255
            # Issue a fake connection to wake up the server and allow it to
 
256
            # finish quickly
 
257
            fake_conn = osutils.connect_socket((self.host, self.port))
 
258
            fake_conn.close()
 
259
        except socket.error:
 
260
            # We might have already closed it.  We don't care.
 
261
            pass
 
262
        self.host = None
 
263
        self.port = None
 
264
        self._thread.join()
 
265
        if 'threads' in tests.selftest_debug_flags:
 
266
            sys.stderr.write('Thread  joined: %s\n' % (self._thread.ident,))
 
267
 
 
268
 
 
269
class TestAuthHeader(tests.TestCase):
 
270
 
 
271
    def parse_header(self, header, auth_handler_class=None):
 
272
        if auth_handler_class is None:
 
273
            auth_handler_class = _urllib2_wrappers.AbstractAuthHandler
 
274
        self.auth_handler =  auth_handler_class()
 
275
        return self.auth_handler._parse_auth_header(header)
 
276
 
 
277
    def test_empty_header(self):
 
278
        scheme, remainder = self.parse_header('')
 
279
        self.assertEqual('', scheme)
 
280
        self.assertIs(None, remainder)
 
281
 
 
282
    def test_negotiate_header(self):
 
283
        scheme, remainder = self.parse_header('Negotiate')
 
284
        self.assertEqual('negotiate', scheme)
 
285
        self.assertIs(None, remainder)
 
286
 
 
287
    def test_basic_header(self):
 
288
        scheme, remainder = self.parse_header(
 
289
            'Basic realm="Thou should not pass"')
 
290
        self.assertEqual('basic', scheme)
 
291
        self.assertEqual('realm="Thou should not pass"', remainder)
 
292
 
 
293
    def test_basic_extract_realm(self):
 
294
        scheme, remainder = self.parse_header(
 
295
            'Basic realm="Thou should not pass"',
 
296
            _urllib2_wrappers.BasicAuthHandler)
 
297
        match, realm = self.auth_handler.extract_realm(remainder)
 
298
        self.assertTrue(match is not None)
 
299
        self.assertEqual('Thou should not pass', realm)
 
300
 
 
301
    def test_digest_header(self):
 
302
        scheme, remainder = self.parse_header(
 
303
            'Digest realm="Thou should not pass"')
 
304
        self.assertEqual('digest', scheme)
 
305
        self.assertEqual('realm="Thou should not pass"', remainder)
 
306
 
 
307
 
 
308
class TestHTTPServer(tests.TestCase):
 
309
    """Test the HTTP servers implementations."""
 
310
 
 
311
    def test_invalid_protocol(self):
 
312
        class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
 
313
 
 
314
            protocol_version = 'HTTP/0.1'
 
315
 
 
316
        self.assertRaises(httplib.UnknownProtocol,
 
317
                          http_server.HttpServer, BogusRequestHandler)
 
318
 
 
319
    def test_force_invalid_protocol(self):
 
320
        self.assertRaises(httplib.UnknownProtocol,
 
321
                          http_server.HttpServer, protocol_version='HTTP/0.1')
 
322
 
 
323
    def test_server_start_and_stop(self):
 
324
        server = http_server.HttpServer()
 
325
        self.addCleanup(server.stop_server)
 
326
        server.start_server()
 
327
        self.assertTrue(server.server is not None)
 
328
        self.assertTrue(server.server.serving is not None)
 
329
        self.assertTrue(server.server.serving)
 
330
 
 
331
    def test_create_http_server_one_zero(self):
 
332
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
 
333
 
 
334
            protocol_version = 'HTTP/1.0'
 
335
 
 
336
        server = http_server.HttpServer(RequestHandlerOneZero)
 
337
        self.start_server(server)
 
338
        self.assertIsInstance(server.server, http_server.TestingHTTPServer)
 
339
 
 
340
    def test_create_http_server_one_one(self):
 
341
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
 
342
 
 
343
            protocol_version = 'HTTP/1.1'
 
344
 
 
345
        server = http_server.HttpServer(RequestHandlerOneOne)
 
346
        self.start_server(server)
 
347
        self.assertIsInstance(server.server,
 
348
                              http_server.TestingThreadingHTTPServer)
 
349
 
 
350
    def test_create_http_server_force_one_one(self):
 
351
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
 
352
 
 
353
            protocol_version = 'HTTP/1.0'
 
354
 
 
355
        server = http_server.HttpServer(RequestHandlerOneZero,
 
356
                                        protocol_version='HTTP/1.1')
 
357
        self.start_server(server)
 
358
        self.assertIsInstance(server.server,
 
359
                              http_server.TestingThreadingHTTPServer)
 
360
 
 
361
    def test_create_http_server_force_one_zero(self):
 
362
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
 
363
 
 
364
            protocol_version = 'HTTP/1.1'
 
365
 
 
366
        server = http_server.HttpServer(RequestHandlerOneOne,
 
367
                                        protocol_version='HTTP/1.0')
 
368
        self.start_server(server)
 
369
        self.assertIsInstance(server.server,
 
370
                              http_server.TestingHTTPServer)
 
371
 
 
372
 
 
373
class TestWithTransport_pycurl(object):
 
374
    """Test case to inherit from if pycurl is present"""
 
375
 
 
376
    def _get_pycurl_maybe(self):
 
377
        self.requireFeature(features.pycurl)
 
378
        return PyCurlTransport
 
379
 
 
380
    _transport = property(_get_pycurl_maybe)
 
381
 
 
382
 
 
383
class TestHttpUrls(tests.TestCase):
 
384
 
 
385
    # TODO: This should be moved to authorization tests once they
 
386
    # are written.
 
387
 
 
388
    def test_url_parsing(self):
 
389
        f = FakeManager()
 
390
        url = http.extract_auth('http://example.com', f)
 
391
        self.assertEqual('http://example.com', url)
 
392
        self.assertEqual(0, len(f.credentials))
 
393
        url = http.extract_auth(
 
394
            'http://user:pass@example.com/bzr/bzr.dev', f)
 
395
        self.assertEqual('http://example.com/bzr/bzr.dev', url)
 
396
        self.assertEqual(1, len(f.credentials))
 
397
        self.assertEqual([None, 'example.com', 'user', 'pass'],
 
398
                         f.credentials[0])
 
399
 
 
400
 
 
401
class TestHttpTransportUrls(tests.TestCase):
 
402
    """Test the http urls."""
 
403
 
 
404
    def test_abs_url(self):
 
405
        """Construction of absolute http URLs"""
 
406
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
407
        eq = self.assertEqualDiff
 
408
        eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
 
409
        eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
 
410
        eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
 
411
        eq(t.abspath('.bzr/1//2/./3'),
 
412
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
 
413
 
 
414
    def test_invalid_http_urls(self):
 
415
        """Trap invalid construction of urls"""
 
416
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
417
        self.assertRaises(errors.InvalidURL,
 
418
                          self._transport,
 
419
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
 
420
 
 
421
    def test_http_root_urls(self):
 
422
        """Construction of URLs from server root"""
 
423
        t = self._transport('http://bzr.ozlabs.org/')
 
424
        eq = self.assertEqualDiff
 
425
        eq(t.abspath('.bzr/tree-version'),
 
426
           'http://bzr.ozlabs.org/.bzr/tree-version')
 
427
 
 
428
    def test_http_impl_urls(self):
 
429
        """There are servers which ask for particular clients to connect"""
 
430
        server = self._server()
 
431
        server.start_server()
 
432
        try:
 
433
            url = server.get_url()
 
434
            self.assertTrue(url.startswith('%s://' % self._url_protocol))
 
435
        finally:
 
436
            server.stop_server()
 
437
 
 
438
 
 
439
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
 
440
 
 
441
    # TODO: This should really be moved into another pycurl
 
442
    # specific test. When https tests will be implemented, take
 
443
    # this one into account.
 
444
    def test_pycurl_without_https_support(self):
 
445
        """Test that pycurl without SSL do not fail with a traceback.
 
446
 
 
447
        For the purpose of the test, we force pycurl to ignore
 
448
        https by supplying a fake version_info that do not
 
449
        support it.
 
450
        """
 
451
        self.requireFeature(features.pycurl)
 
452
        # Import the module locally now that we now it's available.
 
453
        pycurl = features.pycurl.module
 
454
 
 
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')
 
473
 
 
474
 
 
475
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
 
476
    """Test the http connections."""
 
477
 
 
478
    def setUp(self):
 
479
        http_utils.TestCaseWithWebserver.setUp(self)
 
480
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
 
481
                        transport=self.get_transport())
 
482
 
 
483
    def test_http_has(self):
 
484
        server = self.get_readonly_server()
 
485
        t = self.get_readonly_transport()
 
486
        self.assertEqual(t.has('foo/bar'), True)
 
487
        self.assertEqual(len(server.logs), 1)
 
488
        self.assertContainsRe(server.logs[0],
 
489
            r'"HEAD /foo/bar HTTP/1.." (200|302) - "-" "bzr/')
 
490
 
 
491
    def test_http_has_not_found(self):
 
492
        server = self.get_readonly_server()
 
493
        t = self.get_readonly_transport()
 
494
        self.assertEqual(t.has('not-found'), False)
 
495
        self.assertContainsRe(server.logs[1],
 
496
            r'"HEAD /not-found HTTP/1.." 404 - "-" "bzr/')
 
497
 
 
498
    def test_http_get(self):
 
499
        server = self.get_readonly_server()
 
500
        t = self.get_readonly_transport()
 
501
        fp = t.get('foo/bar')
 
502
        self.assertEqualDiff(
 
503
            fp.read(),
 
504
            'contents of foo/bar\n')
 
505
        self.assertEqual(len(server.logs), 1)
 
506
        self.assertTrue(server.logs[0].find(
 
507
            '"GET /foo/bar HTTP/1.1" 200 - "-" "bzr/%s'
 
508
            % bzrlib.__version__) > -1)
 
509
 
 
510
    def test_has_on_bogus_host(self):
 
511
        # Get a free address and don't 'accept' on it, so that we
 
512
        # can be sure there is no http handler there, but set a
 
513
        # reasonable timeout to not slow down tests too much.
 
514
        default_timeout = socket.getdefaulttimeout()
 
515
        try:
 
516
            socket.setdefaulttimeout(2)
 
517
            s = socket.socket()
 
518
            s.bind(('localhost', 0))
 
519
            t = self._transport('http://%s:%s/' % s.getsockname())
 
520
            self.assertRaises(errors.ConnectionError, t.has, 'foo/bar')
 
521
        finally:
 
522
            socket.setdefaulttimeout(default_timeout)
 
523
 
 
524
 
 
525
class TestHttpTransportRegistration(tests.TestCase):
 
526
    """Test registrations of various http implementations"""
 
527
 
 
528
    def test_http_registered(self):
 
529
        t = transport.get_transport('%s://foo.com/' % self._url_protocol)
 
530
        self.assertIsInstance(t, transport.Transport)
 
531
        self.assertIsInstance(t, self._transport)
 
532
 
 
533
 
 
534
class TestPost(tests.TestCase):
 
535
 
 
536
    def test_post_body_is_received(self):
 
537
        server = RecordingServer(expect_body_tail='end-of-body',
 
538
                                 scheme=self._url_protocol)
 
539
        self.start_server(server)
 
540
        url = server.get_url()
 
541
        # FIXME: needs a cleanup -- vila 20100611
 
542
        http_transport = transport.get_transport(url)
 
543
        code, response = http_transport._post('abc def end-of-body')
 
544
        self.assertTrue(
 
545
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
 
546
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
 
547
        # The transport should not be assuming that the server can accept
 
548
        # chunked encoding the first time it connects, because HTTP/1.1, so we
 
549
        # check for the literal string.
 
550
        self.assertTrue(
 
551
            server.received_bytes.endswith('\r\n\r\nabc def end-of-body'))
 
552
 
 
553
 
 
554
class TestRangeHeader(tests.TestCase):
 
555
    """Test range_header method"""
 
556
 
 
557
    def check_header(self, value, ranges=[], tail=0):
 
558
        offsets = [ (start, end - start + 1) for start, end in ranges]
 
559
        coalesce = transport.Transport._coalesce_offsets
 
560
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
 
561
        range_header = http.HttpTransportBase._range_header
 
562
        self.assertEqual(value, range_header(coalesced, tail))
 
563
 
 
564
    def test_range_header_single(self):
 
565
        self.check_header('0-9', ranges=[(0,9)])
 
566
        self.check_header('100-109', ranges=[(100,109)])
 
567
 
 
568
    def test_range_header_tail(self):
 
569
        self.check_header('-10', tail=10)
 
570
        self.check_header('-50', tail=50)
 
571
 
 
572
    def test_range_header_multi(self):
 
573
        self.check_header('0-9,100-200,300-5000',
 
574
                          ranges=[(0,9), (100, 200), (300,5000)])
 
575
 
 
576
    def test_range_header_mixed(self):
 
577
        self.check_header('0-9,300-5000,-50',
 
578
                          ranges=[(0,9), (300,5000)],
 
579
                          tail=50)
 
580
 
 
581
 
 
582
class TestSpecificRequestHandler(http_utils.TestCaseWithWebserver):
 
583
    """Tests a specific request handler.
 
584
 
 
585
    Daughter classes are expected to override _req_handler_class
 
586
    """
 
587
 
 
588
    # Provide a useful default
 
589
    _req_handler_class = http_server.TestingHTTPRequestHandler
 
590
 
 
591
    def create_transport_readonly_server(self):
 
592
        server = http_server.HttpServer(self._req_handler_class,
 
593
                                        protocol_version=self._protocol_version)
 
594
        server._url_protocol = self._url_protocol
 
595
        return server
 
596
 
 
597
    def _testing_pycurl(self):
 
598
        # TODO: This is duplicated for lots of the classes in this file
 
599
        return (features.pycurl.available()
 
600
                and self._transport == PyCurlTransport)
 
601
 
 
602
 
 
603
class WallRequestHandler(http_server.TestingHTTPRequestHandler):
 
604
    """Whatever request comes in, close the connection"""
 
605
 
 
606
    def _handle_one_request(self):
 
607
        """Handle a single HTTP request, by abruptly closing the connection"""
 
608
        self.close_connection = 1
 
609
 
 
610
 
 
611
class TestWallServer(TestSpecificRequestHandler):
 
612
    """Tests exceptions during the connection phase"""
 
613
 
 
614
    _req_handler_class = WallRequestHandler
 
615
 
 
616
    def test_http_has(self):
 
617
        t = self.get_readonly_transport()
 
618
        # Unfortunately httplib (see HTTPResponse._read_status
 
619
        # for details) make no distinction between a closed
 
620
        # socket and badly formatted status line, so we can't
 
621
        # just test for ConnectionError, we have to test
 
622
        # InvalidHttpResponse too. And pycurl may raise ConnectionReset
 
623
        # instead of ConnectionError too.
 
624
        self.assertRaises(( errors.ConnectionError, errors.ConnectionReset,
 
625
                            errors.InvalidHttpResponse),
 
626
                          t.has, 'foo/bar')
 
627
 
 
628
    def test_http_get(self):
 
629
        t = self.get_readonly_transport()
 
630
        self.assertRaises((errors.ConnectionError, errors.ConnectionReset,
 
631
                           errors.InvalidHttpResponse),
 
632
                          t.get, 'foo/bar')
 
633
 
 
634
 
 
635
class BadStatusRequestHandler(http_server.TestingHTTPRequestHandler):
 
636
    """Whatever request comes in, returns a bad status"""
 
637
 
 
638
    def parse_request(self):
 
639
        """Fakes handling a single HTTP request, returns a bad status"""
 
640
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
 
641
        self.send_response(0, "Bad status")
 
642
        self.close_connection = 1
 
643
        return False
 
644
 
 
645
 
 
646
class TestBadStatusServer(TestSpecificRequestHandler):
 
647
    """Tests bad status from server."""
 
648
 
 
649
    _req_handler_class = BadStatusRequestHandler
 
650
 
 
651
    def test_http_has(self):
 
652
        t = self.get_readonly_transport()
 
653
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
 
654
 
 
655
    def test_http_get(self):
 
656
        t = self.get_readonly_transport()
 
657
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
 
658
 
 
659
 
 
660
class InvalidStatusRequestHandler(http_server.TestingHTTPRequestHandler):
 
661
    """Whatever request comes in, returns an invalid status"""
 
662
 
 
663
    def parse_request(self):
 
664
        """Fakes handling a single HTTP request, returns a bad status"""
 
665
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
 
666
        self.wfile.write("Invalid status line\r\n")
 
667
        # If we don't close the connection pycurl will hang. Since this is a
 
668
        # stress test we don't *have* to respect the protocol, but we don't
 
669
        # have to sabotage it too much either.
 
670
        self.close_connection = True
 
671
        return False
 
672
 
 
673
 
 
674
class TestInvalidStatusServer(TestBadStatusServer):
 
675
    """Tests invalid status from server.
 
676
 
 
677
    Both implementations raises the same error as for a bad status.
 
678
    """
 
679
 
 
680
    _req_handler_class = InvalidStatusRequestHandler
 
681
 
 
682
 
 
683
class BadProtocolRequestHandler(http_server.TestingHTTPRequestHandler):
 
684
    """Whatever request comes in, returns a bad protocol version"""
 
685
 
 
686
    def parse_request(self):
 
687
        """Fakes handling a single HTTP request, returns a bad status"""
 
688
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
 
689
        # Returns an invalid protocol version, but curl just
 
690
        # ignores it and those cannot be tested.
 
691
        self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
 
692
                                           404,
 
693
                                           'Look at my protocol version'))
 
694
        return False
 
695
 
 
696
 
 
697
class TestBadProtocolServer(TestSpecificRequestHandler):
 
698
    """Tests bad protocol from server."""
 
699
 
 
700
    _req_handler_class = BadProtocolRequestHandler
 
701
 
 
702
    def setUp(self):
 
703
        if self._testing_pycurl():
 
704
            raise tests.TestNotApplicable(
 
705
                "pycurl doesn't check the protocol version")
 
706
        super(TestBadProtocolServer, self).setUp()
 
707
 
 
708
    def test_http_has(self):
 
709
        t = self.get_readonly_transport()
 
710
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
 
711
 
 
712
    def test_http_get(self):
 
713
        t = self.get_readonly_transport()
 
714
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
 
715
 
 
716
 
 
717
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
 
718
    """Whatever request comes in, returns a 403 code"""
 
719
 
 
720
    def parse_request(self):
 
721
        """Handle a single HTTP request, by replying we cannot handle it"""
 
722
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
 
723
        self.send_error(403)
 
724
        return False
 
725
 
 
726
 
 
727
class TestForbiddenServer(TestSpecificRequestHandler):
 
728
    """Tests forbidden server"""
 
729
 
 
730
    _req_handler_class = ForbiddenRequestHandler
 
731
 
 
732
    def test_http_has(self):
 
733
        t = self.get_readonly_transport()
 
734
        self.assertRaises(errors.TransportError, t.has, 'foo/bar')
 
735
 
 
736
    def test_http_get(self):
 
737
        t = self.get_readonly_transport()
 
738
        self.assertRaises(errors.TransportError, t.get, 'foo/bar')
 
739
 
 
740
 
 
741
class TestRecordingServer(tests.TestCase):
 
742
 
 
743
    def test_create(self):
 
744
        server = RecordingServer(expect_body_tail=None)
 
745
        self.assertEqual('', server.received_bytes)
 
746
        self.assertEqual(None, server.host)
 
747
        self.assertEqual(None, server.port)
 
748
 
 
749
    def test_setUp_and_stop(self):
 
750
        server = RecordingServer(expect_body_tail=None)
 
751
        server.start_server()
 
752
        try:
 
753
            self.assertNotEqual(None, server.host)
 
754
            self.assertNotEqual(None, server.port)
 
755
        finally:
 
756
            server.stop_server()
 
757
        self.assertEqual(None, server.host)
 
758
        self.assertEqual(None, server.port)
 
759
 
 
760
    def test_send_receive_bytes(self):
 
761
        server = RecordingServer(expect_body_tail='c', scheme='http')
 
762
        self.start_server(server)
 
763
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
764
        sock.connect((server.host, server.port))
 
765
        sock.sendall('abc')
 
766
        self.assertEqual('HTTP/1.1 200 OK\r\n',
 
767
                         osutils.recv_all(sock, 4096))
 
768
        self.assertEqual('abc', server.received_bytes)
 
769
 
 
770
 
 
771
class TestRangeRequestServer(TestSpecificRequestHandler):
 
772
    """Tests readv requests against server.
 
773
 
 
774
    We test against default "normal" server.
 
775
    """
 
776
 
 
777
    def setUp(self):
 
778
        super(TestRangeRequestServer, self).setUp()
 
779
        self.build_tree_contents([('a', '0123456789')],)
 
780
 
 
781
    def test_readv(self):
 
782
        t = self.get_readonly_transport()
 
783
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
 
784
        self.assertEqual(l[0], (0, '0'))
 
785
        self.assertEqual(l[1], (1, '1'))
 
786
        self.assertEqual(l[2], (3, '34'))
 
787
        self.assertEqual(l[3], (9, '9'))
 
788
 
 
789
    def test_readv_out_of_order(self):
 
790
        t = self.get_readonly_transport()
 
791
        l = list(t.readv('a', ((1, 1), (9, 1), (0, 1), (3, 2))))
 
792
        self.assertEqual(l[0], (1, '1'))
 
793
        self.assertEqual(l[1], (9, '9'))
 
794
        self.assertEqual(l[2], (0, '0'))
 
795
        self.assertEqual(l[3], (3, '34'))
 
796
 
 
797
    def test_readv_invalid_ranges(self):
 
798
        t = self.get_readonly_transport()
 
799
 
 
800
        # This is intentionally reading off the end of the file
 
801
        # since we are sure that it cannot get there
 
802
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
 
803
                              t.readv, 'a', [(1,1), (8,10)])
 
804
 
 
805
        # This is trying to seek past the end of the file, it should
 
806
        # also raise a special error
 
807
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
 
808
                              t.readv, 'a', [(12,2)])
 
809
 
 
810
    def test_readv_multiple_get_requests(self):
 
811
        server = self.get_readonly_server()
 
812
        t = self.get_readonly_transport()
 
813
        # force transport to issue multiple requests
 
814
        t._max_readv_combine = 1
 
815
        t._max_get_ranges = 1
 
816
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
 
817
        self.assertEqual(l[0], (0, '0'))
 
818
        self.assertEqual(l[1], (1, '1'))
 
819
        self.assertEqual(l[2], (3, '34'))
 
820
        self.assertEqual(l[3], (9, '9'))
 
821
        # The server should have issued 4 requests
 
822
        self.assertEqual(4, server.GET_request_nb)
 
823
 
 
824
    def test_readv_get_max_size(self):
 
825
        server = self.get_readonly_server()
 
826
        t = self.get_readonly_transport()
 
827
        # force transport to issue multiple requests by limiting the number of
 
828
        # bytes by request. Note that this apply to coalesced offsets only, a
 
829
        # single range will keep its size even if bigger than the limit.
 
830
        t._get_max_size = 2
 
831
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
832
        self.assertEqual(l[0], (0, '0'))
 
833
        self.assertEqual(l[1], (1, '1'))
 
834
        self.assertEqual(l[2], (2, '2345'))
 
835
        self.assertEqual(l[3], (6, '6789'))
 
836
        # The server should have issued 3 requests
 
837
        self.assertEqual(3, server.GET_request_nb)
 
838
 
 
839
    def test_complete_readv_leave_pipe_clean(self):
 
840
        server = self.get_readonly_server()
 
841
        t = self.get_readonly_transport()
 
842
        # force transport to issue multiple requests
 
843
        t._get_max_size = 2
 
844
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
845
        # The server should have issued 3 requests
 
846
        self.assertEqual(3, server.GET_request_nb)
 
847
        self.assertEqual('0123456789', t.get_bytes('a'))
 
848
        self.assertEqual(4, server.GET_request_nb)
 
849
 
 
850
    def test_incomplete_readv_leave_pipe_clean(self):
 
851
        server = self.get_readonly_server()
 
852
        t = self.get_readonly_transport()
 
853
        # force transport to issue multiple requests
 
854
        t._get_max_size = 2
 
855
        # Don't collapse readv results into a list so that we leave unread
 
856
        # bytes on the socket
 
857
        ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
 
858
        self.assertEqual((0, '0'), ireadv.next())
 
859
        # The server should have issued one request so far
 
860
        self.assertEqual(1, server.GET_request_nb)
 
861
        self.assertEqual('0123456789', t.get_bytes('a'))
 
862
        # get_bytes issued an additional request, the readv pending ones are
 
863
        # lost
 
864
        self.assertEqual(2, server.GET_request_nb)
 
865
 
 
866
 
 
867
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
 
868
    """Always reply to range request as if they were single.
 
869
 
 
870
    Don't be explicit about it, just to annoy the clients.
 
871
    """
 
872
 
 
873
    def get_multiple_ranges(self, file, file_size, ranges):
 
874
        """Answer as if it was a single range request and ignores the rest"""
 
875
        (start, end) = ranges[0]
 
876
        return self.get_single_range(file, file_size, start, end)
 
877
 
 
878
 
 
879
class TestSingleRangeRequestServer(TestRangeRequestServer):
 
880
    """Test readv against a server which accept only single range requests"""
 
881
 
 
882
    _req_handler_class = SingleRangeRequestHandler
 
883
 
 
884
 
 
885
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
 
886
    """Only reply to simple range requests, errors out on multiple"""
 
887
 
 
888
    def get_multiple_ranges(self, file, file_size, ranges):
 
889
        """Refuses the multiple ranges request"""
 
890
        if len(ranges) > 1:
 
891
            file.close()
 
892
            self.send_error(416, "Requested range not satisfiable")
 
893
            return
 
894
        (start, end) = ranges[0]
 
895
        return self.get_single_range(file, file_size, start, end)
 
896
 
 
897
 
 
898
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
 
899
    """Test readv against a server which only accept single range requests"""
 
900
 
 
901
    _req_handler_class = SingleOnlyRangeRequestHandler
 
902
 
 
903
 
 
904
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
 
905
    """Ignore range requests without notice"""
 
906
 
 
907
    def do_GET(self):
 
908
        # Update the statistics
 
909
        self.server.test_case_server.GET_request_nb += 1
 
910
        # Just bypass the range handling done by TestingHTTPRequestHandler
 
911
        return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
 
912
 
 
913
 
 
914
class TestNoRangeRequestServer(TestRangeRequestServer):
 
915
    """Test readv against a server which do not accept range requests"""
 
916
 
 
917
    _req_handler_class = NoRangeRequestHandler
 
918
 
 
919
 
 
920
class MultipleRangeWithoutContentLengthRequestHandler(
 
921
    http_server.TestingHTTPRequestHandler):
 
922
    """Reply to multiple range requests without content length header."""
 
923
 
 
924
    def get_multiple_ranges(self, file, file_size, ranges):
 
925
        self.send_response(206)
 
926
        self.send_header('Accept-Ranges', 'bytes')
 
927
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
 
928
        self.send_header("Content-Type",
 
929
                         "multipart/byteranges; boundary=%s" % boundary)
 
930
        self.end_headers()
 
931
        for (start, end) in ranges:
 
932
            self.wfile.write("--%s\r\n" % boundary)
 
933
            self.send_header("Content-type", 'application/octet-stream')
 
934
            self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
 
935
                                                                  end,
 
936
                                                                  file_size))
 
937
            self.end_headers()
 
938
            self.send_range_content(file, start, end - start + 1)
 
939
        # Final boundary
 
940
        self.wfile.write("--%s\r\n" % boundary)
 
941
 
 
942
 
 
943
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
 
944
 
 
945
    _req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
 
946
 
 
947
 
 
948
class TruncatedMultipleRangeRequestHandler(
 
949
    http_server.TestingHTTPRequestHandler):
 
950
    """Reply to multiple range requests truncating the last ones.
 
951
 
 
952
    This server generates responses whose Content-Length describes all the
 
953
    ranges, but fail to include the last ones leading to client short reads.
 
954
    This has been observed randomly with lighttpd (bug #179368).
 
955
    """
 
956
 
 
957
    _truncated_ranges = 2
 
958
 
 
959
    def get_multiple_ranges(self, file, file_size, ranges):
 
960
        self.send_response(206)
 
961
        self.send_header('Accept-Ranges', 'bytes')
 
962
        boundary = 'tagada'
 
963
        self.send_header('Content-Type',
 
964
                         'multipart/byteranges; boundary=%s' % boundary)
 
965
        boundary_line = '--%s\r\n' % boundary
 
966
        # Calculate the Content-Length
 
967
        content_length = 0
 
968
        for (start, end) in ranges:
 
969
            content_length += len(boundary_line)
 
970
            content_length += self._header_line_length(
 
971
                'Content-type', 'application/octet-stream')
 
972
            content_length += self._header_line_length(
 
973
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
 
974
            content_length += len('\r\n') # end headers
 
975
            content_length += end - start # + 1
 
976
        content_length += len(boundary_line)
 
977
        self.send_header('Content-length', content_length)
 
978
        self.end_headers()
 
979
 
 
980
        # Send the multipart body
 
981
        cur = 0
 
982
        for (start, end) in ranges:
 
983
            self.wfile.write(boundary_line)
 
984
            self.send_header('Content-type', 'application/octet-stream')
 
985
            self.send_header('Content-Range', 'bytes %d-%d/%d'
 
986
                             % (start, end, file_size))
 
987
            self.end_headers()
 
988
            if cur + self._truncated_ranges >= len(ranges):
 
989
                # Abruptly ends the response and close the connection
 
990
                self.close_connection = 1
 
991
                return
 
992
            self.send_range_content(file, start, end - start + 1)
 
993
            cur += 1
 
994
        # No final boundary
 
995
        self.wfile.write(boundary_line)
 
996
 
 
997
 
 
998
class TestTruncatedMultipleRangeServer(TestSpecificRequestHandler):
 
999
 
 
1000
    _req_handler_class = TruncatedMultipleRangeRequestHandler
 
1001
 
 
1002
    def setUp(self):
 
1003
        super(TestTruncatedMultipleRangeServer, self).setUp()
 
1004
        self.build_tree_contents([('a', '0123456789')],)
 
1005
 
 
1006
    def test_readv_with_short_reads(self):
 
1007
        server = self.get_readonly_server()
 
1008
        t = self.get_readonly_transport()
 
1009
        # Force separate ranges for each offset
 
1010
        t._bytes_to_read_before_seek = 0
 
1011
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
 
1012
        self.assertEqual((0, '0'), ireadv.next())
 
1013
        self.assertEqual((2, '2'), ireadv.next())
 
1014
        if not self._testing_pycurl():
 
1015
            # Only one request have been issued so far (except for pycurl that
 
1016
            # try to read the whole response at once)
 
1017
            self.assertEqual(1, server.GET_request_nb)
 
1018
        self.assertEqual((4, '45'), ireadv.next())
 
1019
        self.assertEqual((9, '9'), ireadv.next())
 
1020
        # Both implementations issue 3 requests but:
 
1021
        # - urllib does two multiple (4 ranges, then 2 ranges) then a single
 
1022
        #   range,
 
1023
        # - pycurl does two multiple (4 ranges, 4 ranges) then a single range
 
1024
        self.assertEqual(3, server.GET_request_nb)
 
1025
        # Finally the client have tried a single range request and stays in
 
1026
        # that mode
 
1027
        self.assertEqual('single', t._range_hint)
 
1028
 
 
1029
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
 
1030
    """Errors out when range specifiers exceed the limit"""
 
1031
 
 
1032
    def get_multiple_ranges(self, file, file_size, ranges):
 
1033
        """Refuses the multiple ranges request"""
 
1034
        tcs = self.server.test_case_server
 
1035
        if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
 
1036
            file.close()
 
1037
            # Emulate apache behavior
 
1038
            self.send_error(400, "Bad Request")
 
1039
            return
 
1040
        return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
 
1041
            self, file, file_size, ranges)
 
1042
 
 
1043
 
 
1044
class LimitedRangeHTTPServer(http_server.HttpServer):
 
1045
    """An HttpServer erroring out on requests with too much range specifiers"""
 
1046
 
 
1047
    def __init__(self, request_handler=LimitedRangeRequestHandler,
 
1048
                 protocol_version=None,
 
1049
                 range_limit=None):
 
1050
        http_server.HttpServer.__init__(self, request_handler,
 
1051
                                        protocol_version=protocol_version)
 
1052
        self.range_limit = range_limit
 
1053
 
 
1054
 
 
1055
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
 
1056
    """Tests readv requests against a server erroring out on too much ranges."""
 
1057
 
 
1058
    # Requests with more range specifiers will error out
 
1059
    range_limit = 3
 
1060
 
 
1061
    def create_transport_readonly_server(self):
 
1062
        return LimitedRangeHTTPServer(range_limit=self.range_limit,
 
1063
                                      protocol_version=self._protocol_version)
 
1064
 
 
1065
    def setUp(self):
 
1066
        http_utils.TestCaseWithWebserver.setUp(self)
 
1067
        # We need to manipulate ranges that correspond to real chunks in the
 
1068
        # response, so we build a content appropriately.
 
1069
        filler = ''.join(['abcdefghij' for x in range(102)])
 
1070
        content = ''.join(['%04d' % v + filler for v in range(16)])
 
1071
        self.build_tree_contents([('a', content)],)
 
1072
 
 
1073
    def test_few_ranges(self):
 
1074
        t = self.get_readonly_transport()
 
1075
        l = list(t.readv('a', ((0, 4), (1024, 4), )))
 
1076
        self.assertEqual(l[0], (0, '0000'))
 
1077
        self.assertEqual(l[1], (1024, '0001'))
 
1078
        self.assertEqual(1, self.get_readonly_server().GET_request_nb)
 
1079
 
 
1080
    def test_more_ranges(self):
 
1081
        t = self.get_readonly_transport()
 
1082
        l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
 
1083
        self.assertEqual(l[0], (0, '0000'))
 
1084
        self.assertEqual(l[1], (1024, '0001'))
 
1085
        self.assertEqual(l[2], (4096, '0004'))
 
1086
        self.assertEqual(l[3], (8192, '0008'))
 
1087
        # The server will refuse to serve the first request (too much ranges),
 
1088
        # a second request will succeed.
 
1089
        self.assertEqual(2, self.get_readonly_server().GET_request_nb)
 
1090
 
 
1091
 
 
1092
class TestHttpProxyWhiteBox(tests.TestCase):
 
1093
    """Whitebox test proxy http authorization.
 
1094
 
 
1095
    Only the urllib implementation is tested here.
 
1096
    """
 
1097
 
 
1098
    def setUp(self):
 
1099
        tests.TestCase.setUp(self)
 
1100
        self._old_env = {}
 
1101
        self.addCleanup(self._restore_env)
 
1102
 
 
1103
    def _install_env(self, env):
 
1104
        for name, value in env.iteritems():
 
1105
            self._old_env[name] = osutils.set_or_unset_env(name, value)
 
1106
 
 
1107
    def _restore_env(self):
 
1108
        for name, value in self._old_env.iteritems():
 
1109
            osutils.set_or_unset_env(name, value)
 
1110
 
 
1111
    def _proxied_request(self):
 
1112
        handler = _urllib2_wrappers.ProxyHandler()
 
1113
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
 
1114
        handler.set_proxy(request, 'http')
 
1115
        return request
 
1116
 
 
1117
    def test_empty_user(self):
 
1118
        self._install_env({'http_proxy': 'http://bar.com'})
 
1119
        request = self._proxied_request()
 
1120
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
 
1121
 
 
1122
    def test_invalid_proxy(self):
 
1123
        """A proxy env variable without scheme"""
 
1124
        self._install_env({'http_proxy': 'host:1234'})
 
1125
        self.assertRaises(errors.InvalidURL, self._proxied_request)
 
1126
 
 
1127
 
 
1128
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
 
1129
    """Tests proxy server.
 
1130
 
 
1131
    Be aware that we do not setup a real proxy here. Instead, we
 
1132
    check that the *connection* goes through the proxy by serving
 
1133
    different content (the faked proxy server append '-proxied'
 
1134
    to the file names).
 
1135
    """
 
1136
 
 
1137
    # FIXME: We don't have an https server available, so we don't
 
1138
    # test https connections. --vila toolongago
 
1139
 
 
1140
    def setUp(self):
 
1141
        super(TestProxyHttpServer, self).setUp()
 
1142
        self.transport_secondary_server = http_utils.ProxyServer
 
1143
        self.build_tree_contents([('foo', 'contents of foo\n'),
 
1144
                                  ('foo-proxied', 'proxied contents of foo\n')])
 
1145
        # Let's setup some attributes for tests
 
1146
        server = self.get_readonly_server()
 
1147
        self.server_host_port = '%s:%d' % (server.host, server.port)
 
1148
        if self._testing_pycurl():
 
1149
            # Oh my ! pycurl does not check for the port as part of
 
1150
            # no_proxy :-( So we just test the host part
 
1151
            self.no_proxy_host = server.host
 
1152
        else:
 
1153
            self.no_proxy_host = self.server_host_port
 
1154
        # The secondary server is the proxy
 
1155
        self.proxy_url = self.get_secondary_url()
 
1156
        self._old_env = {}
 
1157
 
 
1158
    def _testing_pycurl(self):
 
1159
        # TODO: This is duplicated for lots of the classes in this file
 
1160
        return (features.pycurl.available()
 
1161
                and self._transport == PyCurlTransport)
 
1162
 
 
1163
    def _install_env(self, env):
 
1164
        for name, value in env.iteritems():
 
1165
            self._old_env[name] = osutils.set_or_unset_env(name, value)
 
1166
 
 
1167
    def _restore_env(self):
 
1168
        for name, value in self._old_env.iteritems():
 
1169
            osutils.set_or_unset_env(name, value)
 
1170
 
 
1171
    def proxied_in_env(self, env):
 
1172
        self._install_env(env)
 
1173
        t = self.get_readonly_transport()
 
1174
        try:
 
1175
            self.assertEqual('proxied contents of foo\n', t.get('foo').read())
 
1176
        finally:
 
1177
            self._restore_env()
 
1178
 
 
1179
    def not_proxied_in_env(self, env):
 
1180
        self._install_env(env)
 
1181
        t = self.get_readonly_transport()
 
1182
        try:
 
1183
            self.assertEqual('contents of foo\n', t.get('foo').read())
 
1184
        finally:
 
1185
            self._restore_env()
 
1186
 
 
1187
    def test_http_proxy(self):
 
1188
        self.proxied_in_env({'http_proxy': self.proxy_url})
 
1189
 
 
1190
    def test_HTTP_PROXY(self):
 
1191
        if self._testing_pycurl():
 
1192
            # pycurl does not check HTTP_PROXY for security reasons
 
1193
            # (for use in a CGI context that we do not care
 
1194
            # about. Should we ?)
 
1195
            raise tests.TestNotApplicable(
 
1196
                'pycurl does not check HTTP_PROXY for security reasons')
 
1197
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
 
1198
 
 
1199
    def test_all_proxy(self):
 
1200
        self.proxied_in_env({'all_proxy': self.proxy_url})
 
1201
 
 
1202
    def test_ALL_PROXY(self):
 
1203
        self.proxied_in_env({'ALL_PROXY': self.proxy_url})
 
1204
 
 
1205
    def test_http_proxy_with_no_proxy(self):
 
1206
        self.not_proxied_in_env({'http_proxy': self.proxy_url,
 
1207
                                 'no_proxy': self.no_proxy_host})
 
1208
 
 
1209
    def test_HTTP_PROXY_with_NO_PROXY(self):
 
1210
        if self._testing_pycurl():
 
1211
            raise tests.TestNotApplicable(
 
1212
                'pycurl does not check HTTP_PROXY for security reasons')
 
1213
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
 
1214
                                 'NO_PROXY': self.no_proxy_host})
 
1215
 
 
1216
    def test_all_proxy_with_no_proxy(self):
 
1217
        self.not_proxied_in_env({'all_proxy': self.proxy_url,
 
1218
                                 'no_proxy': self.no_proxy_host})
 
1219
 
 
1220
    def test_ALL_PROXY_with_NO_PROXY(self):
 
1221
        self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
 
1222
                                 'NO_PROXY': self.no_proxy_host})
 
1223
 
 
1224
    def test_http_proxy_without_scheme(self):
 
1225
        if self._testing_pycurl():
 
1226
            # pycurl *ignores* invalid proxy env variables. If that ever change
 
1227
            # in the future, this test will fail indicating that pycurl do not
 
1228
            # ignore anymore such variables.
 
1229
            self.not_proxied_in_env({'http_proxy': self.server_host_port})
 
1230
        else:
 
1231
            self.assertRaises(errors.InvalidURL,
 
1232
                              self.proxied_in_env,
 
1233
                              {'http_proxy': self.server_host_port})
 
1234
 
 
1235
 
 
1236
class TestRanges(http_utils.TestCaseWithWebserver):
 
1237
    """Test the Range header in GET methods."""
 
1238
 
 
1239
    def setUp(self):
 
1240
        http_utils.TestCaseWithWebserver.setUp(self)
 
1241
        self.build_tree_contents([('a', '0123456789')],)
 
1242
 
 
1243
    def create_transport_readonly_server(self):
 
1244
        return http_server.HttpServer(protocol_version=self._protocol_version)
 
1245
 
 
1246
    def _file_contents(self, relpath, ranges):
 
1247
        t = self.get_readonly_transport()
 
1248
        offsets = [ (start, end - start + 1) for start, end in ranges]
 
1249
        coalesce = t._coalesce_offsets
 
1250
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
 
1251
        code, data = t._get(relpath, coalesced)
 
1252
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
 
1253
        for start, end in ranges:
 
1254
            data.seek(start)
 
1255
            yield data.read(end - start + 1)
 
1256
 
 
1257
    def _file_tail(self, relpath, tail_amount):
 
1258
        t = self.get_readonly_transport()
 
1259
        code, data = t._get(relpath, [], tail_amount)
 
1260
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
 
1261
        data.seek(-tail_amount, 2)
 
1262
        return data.read(tail_amount)
 
1263
 
 
1264
    def test_range_header(self):
 
1265
        # Valid ranges
 
1266
        map(self.assertEqual,['0', '234'],
 
1267
            list(self._file_contents('a', [(0,0), (2,4)])),)
 
1268
 
 
1269
    def test_range_header_tail(self):
 
1270
        self.assertEqual('789', self._file_tail('a', 3))
 
1271
 
 
1272
    def test_syntactically_invalid_range_header(self):
 
1273
        self.assertListRaises(errors.InvalidHttpRange,
 
1274
                          self._file_contents, 'a', [(4, 3)])
 
1275
 
 
1276
    def test_semantically_invalid_range_header(self):
 
1277
        self.assertListRaises(errors.InvalidHttpRange,
 
1278
                          self._file_contents, 'a', [(42, 128)])
 
1279
 
 
1280
 
 
1281
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
 
1282
    """Test redirection between http servers."""
 
1283
 
 
1284
    def setUp(self):
 
1285
        super(TestHTTPRedirections, self).setUp()
 
1286
        self.build_tree_contents([('a', '0123456789'),
 
1287
                                  ('bundle',
 
1288
                                  '# Bazaar revision bundle v0.9\n#\n')
 
1289
                                  ],)
 
1290
 
 
1291
    def test_redirected(self):
 
1292
        self.assertRaises(errors.RedirectRequested,
 
1293
                          self.get_old_transport().get, 'a')
 
1294
        self.assertEqual('0123456789', self.get_new_transport().get('a').read())
 
1295
 
 
1296
 
 
1297
class RedirectedRequest(_urllib2_wrappers.Request):
 
1298
    """Request following redirections. """
 
1299
 
 
1300
    init_orig = _urllib2_wrappers.Request.__init__
 
1301
 
 
1302
    def __init__(self, method, url, *args, **kwargs):
 
1303
        """Constructor.
 
1304
 
 
1305
        """
 
1306
        # Since the tests using this class will replace
 
1307
        # _urllib2_wrappers.Request, we can't just call the base class __init__
 
1308
        # or we'll loop.
 
1309
        RedirectedRequest.init_orig(self, method, url, *args, **kwargs)
 
1310
        self.follow_redirections = True
 
1311
 
 
1312
 
 
1313
def install_redirected_request(test):
 
1314
    test.overrideAttr(_urllib2_wrappers, 'Request', RedirectedRequest)
 
1315
 
 
1316
 
 
1317
def cleanup_http_redirection_connections(test):
 
1318
    # Some sockets are opened but never seen by _urllib, so we trap them at
 
1319
    # the _urllib2_wrappers level to be able to clean them up.
 
1320
    def socket_disconnect(sock):
 
1321
        try:
 
1322
            sock.shutdown(socket.SHUT_RDWR)
 
1323
            sock.close()
 
1324
        except socket.error:
 
1325
            pass
 
1326
    def connect(connection):
 
1327
        test.http_connect_orig(connection)
 
1328
        test.addCleanup(socket_disconnect, connection.sock)
 
1329
    test.http_connect_orig = test.overrideAttr(
 
1330
        _urllib2_wrappers.HTTPConnection, 'connect', connect)
 
1331
    def connect(connection):
 
1332
        test.https_connect_orig(connection)
 
1333
        test.addCleanup(socket_disconnect, connection.sock)
 
1334
    test.https_connect_orig = test.overrideAttr(
 
1335
        _urllib2_wrappers.HTTPSConnection, 'connect', connect)
 
1336
 
 
1337
 
 
1338
class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):
 
1339
    """Test redirections.
 
1340
 
 
1341
    http implementations do not redirect silently anymore (they
 
1342
    do not redirect at all in fact). The mechanism is still in
 
1343
    place at the _urllib2_wrappers.Request level and these tests
 
1344
    exercise it.
 
1345
 
 
1346
    For the pycurl implementation
 
1347
    the redirection have been deleted as we may deprecate pycurl
 
1348
    and I have no place to keep a working implementation.
 
1349
    -- vila 20070212
 
1350
    """
 
1351
 
 
1352
    def setUp(self):
 
1353
        if (features.pycurl.available()
 
1354
            and self._transport == PyCurlTransport):
 
1355
            raise tests.TestNotApplicable(
 
1356
                "pycurl doesn't redirect silently anymore")
 
1357
        super(TestHTTPSilentRedirections, self).setUp()
 
1358
        install_redirected_request(self)
 
1359
        cleanup_http_redirection_connections(self)
 
1360
        self.build_tree_contents([('a','a'),
 
1361
                                  ('1/',),
 
1362
                                  ('1/a', 'redirected once'),
 
1363
                                  ('2/',),
 
1364
                                  ('2/a', 'redirected twice'),
 
1365
                                  ('3/',),
 
1366
                                  ('3/a', 'redirected thrice'),
 
1367
                                  ('4/',),
 
1368
                                  ('4/a', 'redirected 4 times'),
 
1369
                                  ('5/',),
 
1370
                                  ('5/a', 'redirected 5 times'),
 
1371
                                  ],)
 
1372
 
 
1373
    def test_one_redirection(self):
 
1374
        t = self.get_old_transport()
 
1375
        req = RedirectedRequest('GET', t._remote_path('a'))
 
1376
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
1377
                                       self.new_server.port)
 
1378
        self.old_server.redirections = \
 
1379
            [('(.*)', r'%s/1\1' % (new_prefix), 301),]
 
1380
        self.assertEqual('redirected once', t._perform(req).read())
 
1381
 
 
1382
    def test_five_redirections(self):
 
1383
        t = self.get_old_transport()
 
1384
        req = RedirectedRequest('GET', t._remote_path('a'))
 
1385
        old_prefix = 'http://%s:%s' % (self.old_server.host,
 
1386
                                       self.old_server.port)
 
1387
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
1388
                                       self.new_server.port)
 
1389
        self.old_server.redirections = [
 
1390
            ('/1(.*)', r'%s/2\1' % (old_prefix), 302),
 
1391
            ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
 
1392
            ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
 
1393
            ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
 
1394
            ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
 
1395
            ]
 
1396
        self.assertEqual('redirected 5 times', t._perform(req).read())
 
1397
 
 
1398
 
 
1399
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
 
1400
    """Test transport.do_catching_redirections."""
 
1401
 
 
1402
    def setUp(self):
 
1403
        super(TestDoCatchRedirections, self).setUp()
 
1404
        self.build_tree_contents([('a', '0123456789'),],)
 
1405
        cleanup_http_redirection_connections(self)
 
1406
 
 
1407
        self.old_transport = self.get_old_transport()
 
1408
 
 
1409
    def get_a(self, t):
 
1410
        return t.get('a')
 
1411
 
 
1412
    def test_no_redirection(self):
 
1413
        t = self.get_new_transport()
 
1414
 
 
1415
        # We use None for redirected so that we fail if redirected
 
1416
        self.assertEqual('0123456789',
 
1417
                         transport.do_catching_redirections(
 
1418
                self.get_a, t, None).read())
 
1419
 
 
1420
    def test_one_redirection(self):
 
1421
        self.redirections = 0
 
1422
 
 
1423
        def redirected(t, exception, redirection_notice):
 
1424
            self.redirections += 1
 
1425
            redirected_t = t._redirected_to(exception.source, exception.target)
 
1426
            return redirected_t
 
1427
 
 
1428
        self.assertEqual('0123456789',
 
1429
                         transport.do_catching_redirections(
 
1430
                self.get_a, self.old_transport, redirected).read())
 
1431
        self.assertEqual(1, self.redirections)
 
1432
 
 
1433
    def test_redirection_loop(self):
 
1434
 
 
1435
        def redirected(transport, exception, redirection_notice):
 
1436
            # By using the redirected url as a base dir for the
 
1437
            # *old* transport, we create a loop: a => a/a =>
 
1438
            # a/a/a
 
1439
            return self.old_transport.clone(exception.target)
 
1440
 
 
1441
        self.assertRaises(errors.TooManyRedirections,
 
1442
                          transport.do_catching_redirections,
 
1443
                          self.get_a, self.old_transport, redirected)
 
1444
 
 
1445
 
 
1446
class TestAuth(http_utils.TestCaseWithWebserver):
 
1447
    """Test authentication scheme"""
 
1448
 
 
1449
    _auth_header = 'Authorization'
 
1450
    _password_prompt_prefix = ''
 
1451
    _username_prompt_prefix = ''
 
1452
    # Set by load_tests
 
1453
    _auth_server = None
 
1454
 
 
1455
    def setUp(self):
 
1456
        super(TestAuth, self).setUp()
 
1457
        self.server = self.get_readonly_server()
 
1458
        self.build_tree_contents([('a', 'contents of a\n'),
 
1459
                                  ('b', 'contents of b\n'),])
 
1460
 
 
1461
    def create_transport_readonly_server(self):
 
1462
        server = self._auth_server(protocol_version=self._protocol_version)
 
1463
        server._url_protocol = self._url_protocol
 
1464
        return server
 
1465
 
 
1466
    def _testing_pycurl(self):
 
1467
        # TODO: This is duplicated for lots of the classes in this file
 
1468
        return (features.pycurl.available()
 
1469
                and self._transport == PyCurlTransport)
 
1470
 
 
1471
    def get_user_url(self, user, password):
 
1472
        """Build an url embedding user and password"""
 
1473
        url = '%s://' % self.server._url_protocol
 
1474
        if user is not None:
 
1475
            url += user
 
1476
            if password is not None:
 
1477
                url += ':' + password
 
1478
            url += '@'
 
1479
        url += '%s:%s/' % (self.server.host, self.server.port)
 
1480
        return url
 
1481
 
 
1482
    def get_user_transport(self, user, password):
 
1483
        t = transport.get_transport(self.get_user_url(user, password))
 
1484
        return t
 
1485
 
 
1486
    def test_no_user(self):
 
1487
        self.server.add_user('joe', 'foo')
 
1488
        t = self.get_user_transport(None, None)
 
1489
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
 
1490
        # Only one 'Authentication Required' error should occur
 
1491
        self.assertEqual(1, self.server.auth_required_errors)
 
1492
 
 
1493
    def test_empty_pass(self):
 
1494
        self.server.add_user('joe', '')
 
1495
        t = self.get_user_transport('joe', '')
 
1496
        self.assertEqual('contents of a\n', t.get('a').read())
 
1497
        # Only one 'Authentication Required' error should occur
 
1498
        self.assertEqual(1, self.server.auth_required_errors)
 
1499
 
 
1500
    def test_user_pass(self):
 
1501
        self.server.add_user('joe', 'foo')
 
1502
        t = self.get_user_transport('joe', 'foo')
 
1503
        self.assertEqual('contents of a\n', t.get('a').read())
 
1504
        # Only one 'Authentication Required' error should occur
 
1505
        self.assertEqual(1, self.server.auth_required_errors)
 
1506
 
 
1507
    def test_unknown_user(self):
 
1508
        self.server.add_user('joe', 'foo')
 
1509
        t = self.get_user_transport('bill', 'foo')
 
1510
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
 
1511
        # Two 'Authentication Required' errors should occur (the
 
1512
        # initial 'who are you' and 'I don't know you, who are
 
1513
        # you').
 
1514
        self.assertEqual(2, self.server.auth_required_errors)
 
1515
 
 
1516
    def test_wrong_pass(self):
 
1517
        self.server.add_user('joe', 'foo')
 
1518
        t = self.get_user_transport('joe', 'bar')
 
1519
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
 
1520
        # Two 'Authentication Required' errors should occur (the
 
1521
        # initial 'who are you' and 'this is not you, who are you')
 
1522
        self.assertEqual(2, self.server.auth_required_errors)
 
1523
 
 
1524
    def test_prompt_for_username(self):
 
1525
        if self._testing_pycurl():
 
1526
            raise tests.TestNotApplicable(
 
1527
                'pycurl cannot prompt, it handles auth by embedding'
 
1528
                ' user:pass in urls only')
 
1529
 
 
1530
        self.server.add_user('joe', 'foo')
 
1531
        t = self.get_user_transport(None, None)
 
1532
        stdout = tests.StringIOWrapper()
 
1533
        stderr = tests.StringIOWrapper()
 
1534
        ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
 
1535
                                            stdout=stdout, stderr=stderr)
 
1536
        self.assertEqual('contents of a\n',t.get('a').read())
 
1537
        # stdin should be empty
 
1538
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
1539
        stderr.seek(0)
 
1540
        expected_prompt = self._expected_username_prompt(t._unqualified_scheme)
 
1541
        self.assertEqual(expected_prompt, stderr.read(len(expected_prompt)))
 
1542
        self.assertEqual('', stdout.getvalue())
 
1543
        self._check_password_prompt(t._unqualified_scheme, 'joe',
 
1544
                                    stderr.readline())
 
1545
 
 
1546
    def test_prompt_for_password(self):
 
1547
        if self._testing_pycurl():
 
1548
            raise tests.TestNotApplicable(
 
1549
                'pycurl cannot prompt, it handles auth by embedding'
 
1550
                ' user:pass in urls only')
 
1551
 
 
1552
        self.server.add_user('joe', 'foo')
 
1553
        t = self.get_user_transport('joe', None)
 
1554
        stdout = tests.StringIOWrapper()
 
1555
        stderr = tests.StringIOWrapper()
 
1556
        ui.ui_factory = tests.TestUIFactory(stdin='foo\n',
 
1557
                                            stdout=stdout, stderr=stderr)
 
1558
        self.assertEqual('contents of a\n', t.get('a').read())
 
1559
        # stdin should be empty
 
1560
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
1561
        self._check_password_prompt(t._unqualified_scheme, 'joe',
 
1562
                                    stderr.getvalue())
 
1563
        self.assertEqual('', stdout.getvalue())
 
1564
        # And we shouldn't prompt again for a different request
 
1565
        # against the same transport.
 
1566
        self.assertEqual('contents of b\n',t.get('b').read())
 
1567
        t2 = t.clone()
 
1568
        # And neither against a clone
 
1569
        self.assertEqual('contents of b\n',t2.get('b').read())
 
1570
        # Only one 'Authentication Required' error should occur
 
1571
        self.assertEqual(1, self.server.auth_required_errors)
 
1572
 
 
1573
    def _check_password_prompt(self, scheme, user, actual_prompt):
 
1574
        expected_prompt = (self._password_prompt_prefix
 
1575
                           + ("%s %s@%s:%d, Realm: '%s' password: "
 
1576
                              % (scheme.upper(),
 
1577
                                 user, self.server.host, self.server.port,
 
1578
                                 self.server.auth_realm)))
 
1579
        self.assertEqual(expected_prompt, actual_prompt)
 
1580
 
 
1581
    def _expected_username_prompt(self, scheme):
 
1582
        return (self._username_prompt_prefix
 
1583
                + "%s %s:%d, Realm: '%s' username: " % (scheme.upper(),
 
1584
                                 self.server.host, self.server.port,
 
1585
                                 self.server.auth_realm))
 
1586
 
 
1587
    def test_no_prompt_for_password_when_using_auth_config(self):
 
1588
        if self._testing_pycurl():
 
1589
            raise tests.TestNotApplicable(
 
1590
                'pycurl does not support authentication.conf'
 
1591
                ' since it cannot prompt')
 
1592
 
 
1593
        user =' joe'
 
1594
        password = 'foo'
 
1595
        stdin_content = 'bar\n'  # Not the right password
 
1596
        self.server.add_user(user, password)
 
1597
        t = self.get_user_transport(user, None)
 
1598
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
 
1599
                                            stderr=tests.StringIOWrapper())
 
1600
        # Create a minimal config file with the right password
 
1601
        conf = config.AuthenticationConfig()
 
1602
        conf._get_config().update(
 
1603
            {'httptest': {'scheme': 'http', 'port': self.server.port,
 
1604
                          'user': user, 'password': password}})
 
1605
        conf._save()
 
1606
        # Issue a request to the server to connect
 
1607
        self.assertEqual('contents of a\n',t.get('a').read())
 
1608
        # stdin should have  been left untouched
 
1609
        self.assertEqual(stdin_content, ui.ui_factory.stdin.readline())
 
1610
        # Only one 'Authentication Required' error should occur
 
1611
        self.assertEqual(1, self.server.auth_required_errors)
 
1612
 
 
1613
    def test_user_from_auth_conf(self):
 
1614
        if self._testing_pycurl():
 
1615
            raise tests.TestNotApplicable(
 
1616
                'pycurl does not support authentication.conf')
 
1617
        user = 'joe'
 
1618
        password = 'foo'
 
1619
        self.server.add_user(user, password)
 
1620
        # Create a minimal config file with the right password
 
1621
        conf = config.AuthenticationConfig()
 
1622
        conf._get_config().update(
 
1623
            {'httptest': {'scheme': 'http', 'port': self.server.port,
 
1624
                          'user': user, 'password': password}})
 
1625
        conf._save()
 
1626
        t = self.get_user_transport(None, None)
 
1627
        # Issue a request to the server to connect
 
1628
        self.assertEqual('contents of a\n', t.get('a').read())
 
1629
        # Only one 'Authentication Required' error should occur
 
1630
        self.assertEqual(1, self.server.auth_required_errors)
 
1631
 
 
1632
    def test_changing_nonce(self):
 
1633
        if self._auth_server not in (http_utils.HTTPDigestAuthServer,
 
1634
                                     http_utils.ProxyDigestAuthServer):
 
1635
            raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
 
1636
        if self._testing_pycurl():
 
1637
            raise tests.KnownFailure(
 
1638
                'pycurl does not handle a nonce change')
 
1639
        self.server.add_user('joe', 'foo')
 
1640
        t = self.get_user_transport('joe', 'foo')
 
1641
        self.assertEqual('contents of a\n', t.get('a').read())
 
1642
        self.assertEqual('contents of b\n', t.get('b').read())
 
1643
        # Only one 'Authentication Required' error should have
 
1644
        # occured so far
 
1645
        self.assertEqual(1, self.server.auth_required_errors)
 
1646
        # The server invalidates the current nonce
 
1647
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
 
1648
        self.assertEqual('contents of a\n', t.get('a').read())
 
1649
        # Two 'Authentication Required' errors should occur (the
 
1650
        # initial 'who are you' and a second 'who are you' with the new nonce)
 
1651
        self.assertEqual(2, self.server.auth_required_errors)
 
1652
 
 
1653
 
 
1654
 
 
1655
class TestProxyAuth(TestAuth):
 
1656
    """Test proxy authentication schemes."""
 
1657
 
 
1658
    _auth_header = 'Proxy-authorization'
 
1659
    _password_prompt_prefix = 'Proxy '
 
1660
    _username_prompt_prefix = 'Proxy '
 
1661
 
 
1662
    def setUp(self):
 
1663
        super(TestProxyAuth, self).setUp()
 
1664
        self._old_env = {}
 
1665
        self.addCleanup(self._restore_env)
 
1666
        # Override the contents to avoid false positives
 
1667
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
 
1668
                                  ('b', 'not proxied contents of b\n'),
 
1669
                                  ('a-proxied', 'contents of a\n'),
 
1670
                                  ('b-proxied', 'contents of b\n'),
 
1671
                                  ])
 
1672
 
 
1673
    def get_user_transport(self, user, password):
 
1674
        self._install_env({'all_proxy': self.get_user_url(user, password)})
 
1675
        return TestAuth.get_user_transport(self, user, password)
 
1676
 
 
1677
    def _install_env(self, env):
 
1678
        for name, value in env.iteritems():
 
1679
            self._old_env[name] = osutils.set_or_unset_env(name, value)
 
1680
 
 
1681
    def _restore_env(self):
 
1682
        for name, value in self._old_env.iteritems():
 
1683
            osutils.set_or_unset_env(name, value)
 
1684
 
 
1685
    def test_empty_pass(self):
 
1686
        if self._testing_pycurl():
 
1687
            import pycurl
 
1688
            if pycurl.version_info()[1] < '7.16.0':
 
1689
                raise tests.KnownFailure(
 
1690
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
 
1691
        super(TestProxyAuth, self).test_empty_pass()
 
1692
 
 
1693
 
 
1694
class SampleSocket(object):
 
1695
    """A socket-like object for use in testing the HTTP request handler."""
 
1696
 
 
1697
    def __init__(self, socket_read_content):
 
1698
        """Constructs a sample socket.
 
1699
 
 
1700
        :param socket_read_content: a byte sequence
 
1701
        """
 
1702
        # Use plain python StringIO so we can monkey-patch the close method to
 
1703
        # not discard the contents.
 
1704
        from StringIO import StringIO
 
1705
        self.readfile = StringIO(socket_read_content)
 
1706
        self.writefile = StringIO()
 
1707
        self.writefile.close = lambda: None
 
1708
        self.close = lambda: None
 
1709
 
 
1710
    def makefile(self, mode='r', bufsize=None):
 
1711
        if 'r' in mode:
 
1712
            return self.readfile
 
1713
        else:
 
1714
            return self.writefile
 
1715
 
 
1716
 
 
1717
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
 
1718
 
 
1719
    def setUp(self):
 
1720
        super(SmartHTTPTunnellingTest, self).setUp()
 
1721
        # We use the VFS layer as part of HTTP tunnelling tests.
 
1722
        self._captureVar('BZR_NO_SMART_VFS', None)
 
1723
        self.transport_readonly_server = http_utils.HTTPServerWithSmarts
 
1724
        self.http_server = self.get_readonly_server()
 
1725
 
 
1726
    def create_transport_readonly_server(self):
 
1727
        server = http_utils.HTTPServerWithSmarts(
 
1728
            protocol_version=self._protocol_version)
 
1729
        server._url_protocol = self._url_protocol
 
1730
        return server
 
1731
 
 
1732
    def test_open_bzrdir(self):
 
1733
        branch = self.make_branch('relpath')
 
1734
        url = self.http_server.get_url() + 'relpath'
 
1735
        bd = bzrdir.BzrDir.open(url)
 
1736
        self.addCleanup(bd.transport.disconnect)
 
1737
        self.assertIsInstance(bd, _mod_remote.RemoteBzrDir)
 
1738
 
 
1739
    def test_bulk_data(self):
 
1740
        # We should be able to send and receive bulk data in a single message.
 
1741
        # The 'readv' command in the smart protocol both sends and receives
 
1742
        # bulk data, so we use that.
 
1743
        self.build_tree(['data-file'])
 
1744
        http_transport = transport.get_transport(self.http_server.get_url())
 
1745
        medium = http_transport.get_smart_medium()
 
1746
        # Since we provide the medium, the url below will be mostly ignored
 
1747
        # during the test, as long as the path is '/'.
 
1748
        remote_transport = remote.RemoteTransport('bzr://fake_host/',
 
1749
                                                  medium=medium)
 
1750
        self.assertEqual(
 
1751
            [(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
 
1752
 
 
1753
    def test_http_send_smart_request(self):
 
1754
 
 
1755
        post_body = 'hello\n'
 
1756
        expected_reply_body = 'ok\x012\n'
 
1757
 
 
1758
        http_transport = transport.get_transport(self.http_server.get_url())
 
1759
        medium = http_transport.get_smart_medium()
 
1760
        response = medium.send_http_smart_request(post_body)
 
1761
        reply_body = response.read()
 
1762
        self.assertEqual(expected_reply_body, reply_body)
 
1763
 
 
1764
    def test_smart_http_server_post_request_handler(self):
 
1765
        httpd = self.http_server.server
 
1766
 
 
1767
        socket = SampleSocket(
 
1768
            'POST /.bzr/smart %s \r\n' % self._protocol_version
 
1769
            # HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
 
1770
            # for 1.0)
 
1771
            + 'Content-Length: 6\r\n'
 
1772
            '\r\n'
 
1773
            'hello\n')
 
1774
        # Beware: the ('localhost', 80) below is the
 
1775
        # client_address parameter, but we don't have one because
 
1776
        # we have defined a socket which is not bound to an
 
1777
        # address. The test framework never uses this client
 
1778
        # address, so far...
 
1779
        request_handler = http_utils.SmartRequestHandler(socket,
 
1780
                                                         ('localhost', 80),
 
1781
                                                         httpd)
 
1782
        response = socket.writefile.getvalue()
 
1783
        self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
 
1784
        # This includes the end of the HTTP headers, and all the body.
 
1785
        expected_end_of_response = '\r\n\r\nok\x012\n'
 
1786
        self.assertEndsWith(response, expected_end_of_response)
 
1787
 
 
1788
 
 
1789
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
 
1790
    """No smart server here request handler."""
 
1791
 
 
1792
    def do_POST(self):
 
1793
        self.send_error(403, "Forbidden")
 
1794
 
 
1795
 
 
1796
class SmartClientAgainstNotSmartServer(TestSpecificRequestHandler):
 
1797
    """Test smart client behaviour against an http server without smarts."""
 
1798
 
 
1799
    _req_handler_class = ForbiddenRequestHandler
 
1800
 
 
1801
    def test_probe_smart_server(self):
 
1802
        """Test error handling against server refusing smart requests."""
 
1803
        t = self.get_readonly_transport()
 
1804
        # No need to build a valid smart request here, the server will not even
 
1805
        # try to interpret it.
 
1806
        self.assertRaises(errors.SmartProtocolError,
 
1807
                          t.get_smart_medium().send_http_smart_request,
 
1808
                          'whatever')
 
1809
 
 
1810
 
 
1811
class Test_redirected_to(tests.TestCase):
 
1812
 
 
1813
    def test_redirected_to_subdir(self):
 
1814
        t = self._transport('http://www.example.com/foo')
 
1815
        r = t._redirected_to('http://www.example.com/foo',
 
1816
                             'http://www.example.com/foo/subdir')
 
1817
        self.assertIsInstance(r, type(t))
 
1818
        # Both transports share the some connection
 
1819
        self.assertEqual(t._get_connection(), r._get_connection())
 
1820
 
 
1821
    def test_redirected_to_self_with_slash(self):
 
1822
        t = self._transport('http://www.example.com/foo')
 
1823
        r = t._redirected_to('http://www.example.com/foo',
 
1824
                             'http://www.example.com/foo/')
 
1825
        self.assertIsInstance(r, type(t))
 
1826
        # Both transports share the some connection (one can argue that we
 
1827
        # should return the exact same transport here, but that seems
 
1828
        # overkill).
 
1829
        self.assertEqual(t._get_connection(), r._get_connection())
 
1830
 
 
1831
    def test_redirected_to_host(self):
 
1832
        t = self._transport('http://www.example.com/foo')
 
1833
        r = t._redirected_to('http://www.example.com/foo',
 
1834
                             'http://foo.example.com/foo/subdir')
 
1835
        self.assertIsInstance(r, type(t))
 
1836
 
 
1837
    def test_redirected_to_same_host_sibling_protocol(self):
 
1838
        t = self._transport('http://www.example.com/foo')
 
1839
        r = t._redirected_to('http://www.example.com/foo',
 
1840
                             'https://www.example.com/foo')
 
1841
        self.assertIsInstance(r, type(t))
 
1842
 
 
1843
    def test_redirected_to_same_host_different_protocol(self):
 
1844
        t = self._transport('http://www.example.com/foo')
 
1845
        r = t._redirected_to('http://www.example.com/foo',
 
1846
                             'ftp://www.example.com/foo')
 
1847
        self.assertNotEquals(type(r), type(t))
 
1848
 
 
1849
    def test_redirected_to_different_host_same_user(self):
 
1850
        t = self._transport('http://joe@www.example.com/foo')
 
1851
        r = t._redirected_to('http://www.example.com/foo',
 
1852
                             'https://foo.example.com/foo')
 
1853
        self.assertIsInstance(r, type(t))
 
1854
        self.assertEqual(t._user, r._user)
 
1855
 
 
1856
 
 
1857
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
 
1858
    """Request handler for a unique and pre-defined request.
 
1859
 
 
1860
    The only thing we care about here is how many bytes travel on the wire. But
 
1861
    since we want to measure it for a real http client, we have to send it
 
1862
    correct responses.
 
1863
 
 
1864
    We expect to receive a *single* request nothing more (and we won't even
 
1865
    check what request it is, we just measure the bytes read until an empty
 
1866
    line.
 
1867
    """
 
1868
 
 
1869
    def _handle_one_request(self):
 
1870
        tcs = self.server.test_case_server
 
1871
        requestline = self.rfile.readline()
 
1872
        headers = self.MessageClass(self.rfile, 0)
 
1873
        # We just read: the request, the headers, an empty line indicating the
 
1874
        # end of the headers.
 
1875
        bytes_read = len(requestline)
 
1876
        for line in headers.headers:
 
1877
            bytes_read += len(line)
 
1878
        bytes_read += len('\r\n')
 
1879
        if requestline.startswith('POST'):
 
1880
            # The body should be a single line (or we don't know where it ends
 
1881
            # and we don't want to issue a blocking read)
 
1882
            body = self.rfile.readline()
 
1883
            bytes_read += len(body)
 
1884
        tcs.bytes_read = bytes_read
 
1885
 
 
1886
        # We set the bytes written *before* issuing the write, the client is
 
1887
        # supposed to consume every produced byte *before* checking that value.
 
1888
 
 
1889
        # Doing the oppposite may lead to test failure: we may be interrupted
 
1890
        # after the write but before updating the value. The client can then
 
1891
        # continue and read the value *before* we can update it. And yes,
 
1892
        # this has been observed -- vila 20090129
 
1893
        tcs.bytes_written = len(tcs.canned_response)
 
1894
        self.wfile.write(tcs.canned_response)
 
1895
 
 
1896
 
 
1897
class ActivityServerMixin(object):
 
1898
 
 
1899
    def __init__(self, protocol_version):
 
1900
        super(ActivityServerMixin, self).__init__(
 
1901
            request_handler=PredefinedRequestHandler,
 
1902
            protocol_version=protocol_version)
 
1903
        # Bytes read and written by the server
 
1904
        self.bytes_read = 0
 
1905
        self.bytes_written = 0
 
1906
        self.canned_response = None
 
1907
 
 
1908
 
 
1909
class ActivityHTTPServer(ActivityServerMixin, http_server.HttpServer):
 
1910
    pass
 
1911
 
 
1912
 
 
1913
if tests.HTTPSServerFeature.available():
 
1914
    from bzrlib.tests import https_server
 
1915
    class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
 
1916
        pass
 
1917
 
 
1918
 
 
1919
class TestActivityMixin(object):
 
1920
    """Test socket activity reporting.
 
1921
 
 
1922
    We use a special purpose server to control the bytes sent and received and
 
1923
    be able to predict the activity on the client socket.
 
1924
    """
 
1925
 
 
1926
    def setUp(self):
 
1927
        tests.TestCase.setUp(self)
 
1928
        self.server = self._activity_server(self._protocol_version)
 
1929
        self.server.start_server()
 
1930
        self.activities = {}
 
1931
        def report_activity(t, bytes, direction):
 
1932
            count = self.activities.get(direction, 0)
 
1933
            count += bytes
 
1934
            self.activities[direction] = count
 
1935
 
 
1936
        # We override at class level because constructors may propagate the
 
1937
        # bound method and render instance overriding ineffective (an
 
1938
        # alternative would be to define a specific ui factory instead...)
 
1939
        self.overrideAttr(self._transport, '_report_activity', report_activity)
 
1940
        self.addCleanup(self.server.stop_server)
 
1941
 
 
1942
    def get_transport(self):
 
1943
        t = self._transport(self.server.get_url())
 
1944
        # FIXME: Needs cleanup -- vila 20100611
 
1945
        return t
 
1946
 
 
1947
    def assertActivitiesMatch(self):
 
1948
        self.assertEqual(self.server.bytes_read,
 
1949
                         self.activities.get('write', 0), 'written bytes')
 
1950
        self.assertEqual(self.server.bytes_written,
 
1951
                         self.activities.get('read', 0), 'read bytes')
 
1952
 
 
1953
    def test_get(self):
 
1954
        self.server.canned_response = '''HTTP/1.1 200 OK\r
 
1955
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
 
1956
Server: Apache/2.0.54 (Fedora)\r
 
1957
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
 
1958
ETag: "56691-23-38e9ae00"\r
 
1959
Accept-Ranges: bytes\r
 
1960
Content-Length: 35\r
 
1961
Connection: close\r
 
1962
Content-Type: text/plain; charset=UTF-8\r
 
1963
\r
 
1964
Bazaar-NG meta directory, format 1
 
1965
'''
 
1966
        t = self.get_transport()
 
1967
        self.assertEqual('Bazaar-NG meta directory, format 1\n',
 
1968
                         t.get('foo/bar').read())
 
1969
        self.assertActivitiesMatch()
 
1970
 
 
1971
    def test_has(self):
 
1972
        self.server.canned_response = '''HTTP/1.1 200 OK\r
 
1973
Server: SimpleHTTP/0.6 Python/2.5.2\r
 
1974
Date: Thu, 29 Jan 2009 20:21:47 GMT\r
 
1975
Content-type: application/octet-stream\r
 
1976
Content-Length: 20\r
 
1977
Last-Modified: Thu, 29 Jan 2009 20:21:47 GMT\r
 
1978
\r
 
1979
'''
 
1980
        t = self.get_transport()
 
1981
        self.assertTrue(t.has('foo/bar'))
 
1982
        self.assertActivitiesMatch()
 
1983
 
 
1984
    def test_readv(self):
 
1985
        self.server.canned_response = '''HTTP/1.1 206 Partial Content\r
 
1986
Date: Tue, 11 Jul 2006 04:49:48 GMT\r
 
1987
Server: Apache/2.0.54 (Fedora)\r
 
1988
Last-Modified: Thu, 06 Jul 2006 20:22:05 GMT\r
 
1989
ETag: "238a3c-16ec2-805c5540"\r
 
1990
Accept-Ranges: bytes\r
 
1991
Content-Length: 1534\r
 
1992
Connection: close\r
 
1993
Content-Type: multipart/byteranges; boundary=418470f848b63279b\r
 
1994
\r
 
1995
\r
 
1996
--418470f848b63279b\r
 
1997
Content-type: text/plain; charset=UTF-8\r
 
1998
Content-range: bytes 0-254/93890\r
 
1999
\r
 
2000
mbp@sourcefrog.net-20050309040815-13242001617e4a06
 
2001
mbp@sourcefrog.net-20050309040929-eee0eb3e6d1e7627
 
2002
mbp@sourcefrog.net-20050309040957-6cad07f466bb0bb8
 
2003
mbp@sourcefrog.net-20050309041501-c840e09071de3b67
 
2004
mbp@sourcefrog.net-20050309044615-c24a3250be83220a
 
2005
\r
 
2006
--418470f848b63279b\r
 
2007
Content-type: text/plain; charset=UTF-8\r
 
2008
Content-range: bytes 1000-2049/93890\r
 
2009
\r
 
2010
40-fd4ec249b6b139ab
 
2011
mbp@sourcefrog.net-20050311063625-07858525021f270b
 
2012
mbp@sourcefrog.net-20050311231934-aa3776aff5200bb9
 
2013
mbp@sourcefrog.net-20050311231953-73aeb3a131c3699a
 
2014
mbp@sourcefrog.net-20050311232353-f5e33da490872c6a
 
2015
mbp@sourcefrog.net-20050312071639-0a8f59a34a024ff0
 
2016
mbp@sourcefrog.net-20050312073432-b2c16a55e0d6e9fb
 
2017
mbp@sourcefrog.net-20050312073831-a47c3335ece1920f
 
2018
mbp@sourcefrog.net-20050312085412-13373aa129ccbad3
 
2019
mbp@sourcefrog.net-20050313052251-2bf004cb96b39933
 
2020
mbp@sourcefrog.net-20050313052856-3edd84094687cb11
 
2021
mbp@sourcefrog.net-20050313053233-e30a4f28aef48f9d
 
2022
mbp@sourcefrog.net-20050313053853-7c64085594ff3072
 
2023
mbp@sourcefrog.net-20050313054757-a86c3f5871069e22
 
2024
mbp@sourcefrog.net-20050313061422-418f1f73b94879b9
 
2025
mbp@sourcefrog.net-20050313120651-497bd231b19df600
 
2026
mbp@sourcefrog.net-20050314024931-eae0170ef25a5d1a
 
2027
mbp@sourcefrog.net-20050314025438-d52099f915fe65fc
 
2028
mbp@sourcefrog.net-20050314025539-637a636692c055cf
 
2029
mbp@sourcefrog.net-20050314025737-55eb441f430ab4ba
 
2030
mbp@sourcefrog.net-20050314025901-d74aa93bb7ee8f62
 
2031
mbp@source\r
 
2032
--418470f848b63279b--\r
 
2033
'''
 
2034
        t = self.get_transport()
 
2035
        # Remember that the request is ignored and that the ranges below
 
2036
        # doesn't have to match the canned response.
 
2037
        l = list(t.readv('/foo/bar', ((0, 255), (1000, 1050))))
 
2038
        self.assertEqual(2, len(l))
 
2039
        self.assertActivitiesMatch()
 
2040
 
 
2041
    def test_post(self):
 
2042
        self.server.canned_response = '''HTTP/1.1 200 OK\r
 
2043
Date: Tue, 11 Jul 2006 04:32:56 GMT\r
 
2044
Server: Apache/2.0.54 (Fedora)\r
 
2045
Last-Modified: Sun, 23 Apr 2006 19:35:20 GMT\r
 
2046
ETag: "56691-23-38e9ae00"\r
 
2047
Accept-Ranges: bytes\r
 
2048
Content-Length: 35\r
 
2049
Connection: close\r
 
2050
Content-Type: text/plain; charset=UTF-8\r
 
2051
\r
 
2052
lalala whatever as long as itsssss
 
2053
'''
 
2054
        t = self.get_transport()
 
2055
        # We must send a single line of body bytes, see
 
2056
        # PredefinedRequestHandler._handle_one_request
 
2057
        code, f = t._post('abc def end-of-body\n')
 
2058
        self.assertEqual('lalala whatever as long as itsssss\n', f.read())
 
2059
        self.assertActivitiesMatch()
 
2060
 
 
2061
 
 
2062
class TestActivity(tests.TestCase, TestActivityMixin):
 
2063
 
 
2064
    def setUp(self):
 
2065
        TestActivityMixin.setUp(self)
 
2066
 
 
2067
 
 
2068
class TestNoReportActivity(tests.TestCase, TestActivityMixin):
 
2069
 
 
2070
    # Unlike TestActivity, we are really testing ReportingFileSocket and
 
2071
    # ReportingSocket, so we don't need all the parametrization. Since
 
2072
    # ReportingFileSocket and ReportingSocket are wrappers, it's easier to
 
2073
    # test them through their use by the transport than directly (that's a
 
2074
    # bit less clean but far more simpler and effective).
 
2075
    _activity_server = ActivityHTTPServer
 
2076
    _protocol_version = 'HTTP/1.1'
 
2077
 
 
2078
    def setUp(self):
 
2079
        self._transport =_urllib.HttpTransport_urllib
 
2080
        TestActivityMixin.setUp(self)
 
2081
 
 
2082
    def assertActivitiesMatch(self):
 
2083
        # Nothing to check here
 
2084
        pass
 
2085
 
 
2086
 
 
2087
class TestAuthOnRedirected(http_utils.TestCaseWithRedirectedWebserver):
 
2088
    """Test authentication on the redirected http server."""
 
2089
 
 
2090
    _auth_header = 'Authorization'
 
2091
    _password_prompt_prefix = ''
 
2092
    _username_prompt_prefix = ''
 
2093
    _auth_server = http_utils.HTTPBasicAuthServer
 
2094
    _transport = _urllib.HttpTransport_urllib
 
2095
 
 
2096
    def setUp(self):
 
2097
        super(TestAuthOnRedirected, self).setUp()
 
2098
        self.build_tree_contents([('a','a'),
 
2099
                                  ('1/',),
 
2100
                                  ('1/a', 'redirected once'),
 
2101
                                  ],)
 
2102
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
2103
                                       self.new_server.port)
 
2104
        self.old_server.redirections = [
 
2105
            ('(.*)', r'%s/1\1' % (new_prefix), 301),]
 
2106
        self.old_transport = self.get_old_transport()
 
2107
        self.new_server.add_user('joe', 'foo')
 
2108
        cleanup_http_redirection_connections(self)
 
2109
 
 
2110
    def create_transport_readonly_server(self):
 
2111
        server = self._auth_server(protocol_version=self._protocol_version)
 
2112
        server._url_protocol = self._url_protocol
 
2113
        return server
 
2114
 
 
2115
    def get_a(self, t):
 
2116
        return t.get('a')
 
2117
 
 
2118
    def test_auth_on_redirected_via_do_catching_redirections(self):
 
2119
        self.redirections = 0
 
2120
 
 
2121
        def redirected(t, exception, redirection_notice):
 
2122
            self.redirections += 1
 
2123
            redirected_t = t._redirected_to(exception.source, exception.target)
 
2124
            self.addCleanup(redirected_t.disconnect)
 
2125
            return redirected_t
 
2126
 
 
2127
        stdout = tests.StringIOWrapper()
 
2128
        stderr = tests.StringIOWrapper()
 
2129
        ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
 
2130
                                            stdout=stdout, stderr=stderr)
 
2131
        self.assertEqual('redirected once',
 
2132
                         transport.do_catching_redirections(
 
2133
                self.get_a, self.old_transport, redirected).read())
 
2134
        self.assertEqual(1, self.redirections)
 
2135
        # stdin should be empty
 
2136
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
2137
        # stdout should be empty, stderr will contains the prompts
 
2138
        self.assertEqual('', stdout.getvalue())
 
2139
 
 
2140
    def test_auth_on_redirected_via_following_redirections(self):
 
2141
        self.new_server.add_user('joe', 'foo')
 
2142
        stdout = tests.StringIOWrapper()
 
2143
        stderr = tests.StringIOWrapper()
 
2144
        ui.ui_factory = tests.TestUIFactory(stdin='joe\nfoo\n',
 
2145
                                            stdout=stdout, stderr=stderr)
 
2146
        t = self.old_transport
 
2147
        req = RedirectedRequest('GET', t.abspath('a'))
 
2148
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
2149
                                       self.new_server.port)
 
2150
        self.old_server.redirections = [
 
2151
            ('(.*)', r'%s/1\1' % (new_prefix), 301),]
 
2152
        self.assertEqual('redirected once', t._perform(req).read())
 
2153
        # stdin should be empty
 
2154
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
2155
        # stdout should be empty, stderr will contains the prompts
 
2156
        self.assertEqual('', stdout.getvalue())
 
2157
 
 
2158