~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

move reference material out of User Guide into User Reference

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
# FIXME: This test should be repeated for each available http client
 
18
# implementation; at the moment we have urllib and pycurl.
 
19
 
 
20
# TODO: Should be renamed to bzrlib.transport.http.tests?
 
21
# TODO: What about renaming to bzrlib.tests.transport.http ?
 
22
 
 
23
from cStringIO import StringIO
 
24
import os
 
25
import select
 
26
import socket
 
27
import sys
 
28
import threading
 
29
 
 
30
import bzrlib
 
31
from bzrlib import (
 
32
    config,
 
33
    errors,
 
34
    osutils,
 
35
    tests,
 
36
    ui,
 
37
    urlutils,
 
38
    )
 
39
from bzrlib.tests.HttpServer import (
 
40
    HttpServer,
 
41
    HttpServer_PyCurl,
 
42
    HttpServer_urllib,
 
43
    )
 
44
from bzrlib.tests.HTTPTestUtil import (
 
45
    BadProtocolRequestHandler,
 
46
    BadStatusRequestHandler,
 
47
    ForbiddenRequestHandler,
 
48
    HTTPBasicAuthServer,
 
49
    HTTPDigestAuthServer,
 
50
    HTTPServerRedirecting,
 
51
    InvalidStatusRequestHandler,
 
52
    LimitedRangeHTTPServer,
 
53
    NoRangeRequestHandler,
 
54
    ProxyBasicAuthServer,
 
55
    ProxyDigestAuthServer,
 
56
    ProxyServer,
 
57
    SingleRangeRequestHandler,
 
58
    SingleOnlyRangeRequestHandler,
 
59
    TestCaseWithRedirectedWebserver,
 
60
    TestCaseWithTwoWebservers,
 
61
    TestCaseWithWebserver,
 
62
    WallRequestHandler,
 
63
    )
 
64
from bzrlib.transport import (
 
65
    _CoalescedOffset,
 
66
    do_catching_redirections,
 
67
    get_transport,
 
68
    Transport,
 
69
    )
 
70
from bzrlib.transport.http import (
 
71
    extract_auth,
 
72
    HttpTransportBase,
 
73
    _urllib2_wrappers,
 
74
    )
 
75
from bzrlib.transport.http._urllib import HttpTransport_urllib
 
76
from bzrlib.transport.http._urllib2_wrappers import (
 
77
    ProxyHandler,
 
78
    Request,
 
79
    )
 
80
 
 
81
 
 
82
class FakeManager(object):
 
83
 
 
84
    def __init__(self):
 
85
        self.credentials = []
 
86
 
 
87
    def add_password(self, realm, host, username, password):
 
88
        self.credentials.append([realm, host, username, password])
 
89
 
 
90
 
 
91
class RecordingServer(object):
 
92
    """A fake HTTP server.
 
93
    
 
94
    It records the bytes sent to it, and replies with a 200.
 
95
    """
 
96
 
 
97
    def __init__(self, expect_body_tail=None):
 
98
        """Constructor.
 
99
 
 
100
        :type expect_body_tail: str
 
101
        :param expect_body_tail: a reply won't be sent until this string is
 
102
            received.
 
103
        """
 
104
        self._expect_body_tail = expect_body_tail
 
105
        self.host = None
 
106
        self.port = None
 
107
        self.received_bytes = ''
 
108
 
 
109
    def setUp(self):
 
110
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
111
        self._sock.bind(('127.0.0.1', 0))
 
112
        self.host, self.port = self._sock.getsockname()
 
113
        self._ready = threading.Event()
 
114
        self._thread = threading.Thread(target=self._accept_read_and_reply)
 
115
        self._thread.setDaemon(True)
 
116
        self._thread.start()
 
117
        self._ready.wait(5)
 
118
 
 
119
    def _accept_read_and_reply(self):
 
120
        self._sock.listen(1)
 
121
        self._ready.set()
 
122
        self._sock.settimeout(5)
 
123
        try:
 
124
            conn, address = self._sock.accept()
 
125
            # On win32, the accepted connection will be non-blocking to start
 
126
            # with because we're using settimeout.
 
127
            conn.setblocking(True)
 
128
            while not self.received_bytes.endswith(self._expect_body_tail):
 
129
                self.received_bytes += conn.recv(4096)
 
130
            conn.sendall('HTTP/1.1 200 OK\r\n')
 
131
        except socket.timeout:
 
132
            # Make sure the client isn't stuck waiting for us to e.g. accept.
 
133
            self._sock.close()
 
134
        except socket.error:
 
135
            # The client may have already closed the socket.
 
136
            pass
 
137
 
 
138
    def tearDown(self):
 
139
        try:
 
140
            self._sock.close()
 
141
        except socket.error:
 
142
            # We might have already closed it.  We don't care.
 
143
            pass
 
144
        self.host = None
 
145
        self.port = None
 
146
 
 
147
 
 
148
class TestWithTransport_pycurl(object):
 
149
    """Test case to inherit from if pycurl is present"""
 
150
 
 
151
    def _get_pycurl_maybe(self):
 
152
        try:
 
153
            from bzrlib.transport.http._pycurl import PyCurlTransport
 
154
            return PyCurlTransport
 
155
        except errors.DependencyNotPresent:
 
156
            raise tests.TestSkipped('pycurl not present')
 
157
 
 
158
    _transport = property(_get_pycurl_maybe)
 
159
 
 
160
 
 
161
class TestHttpUrls(tests.TestCase):
 
162
 
 
163
    # TODO: This should be moved to authorization tests once they
 
164
    # are written.
 
165
 
 
166
    def test_url_parsing(self):
 
167
        f = FakeManager()
 
168
        url = extract_auth('http://example.com', f)
 
169
        self.assertEquals('http://example.com', url)
 
170
        self.assertEquals(0, len(f.credentials))
 
171
        url = extract_auth('http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
 
172
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
 
173
        self.assertEquals(1, len(f.credentials))
 
174
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
 
175
                          f.credentials[0])
 
176
 
 
177
 
 
178
class TestHttpTransportUrls(object):
 
179
    """Test the http urls.
 
180
 
 
181
    This MUST be used by daughter classes that also inherit from
 
182
    TestCase.
 
183
 
 
184
    We can't inherit directly from TestCase or the
 
185
    test framework will try to create an instance which cannot
 
186
    run, its implementation being incomplete.
 
187
    """
 
188
 
 
189
    def test_abs_url(self):
 
190
        """Construction of absolute http URLs"""
 
191
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
192
        eq = self.assertEqualDiff
 
193
        eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
 
194
        eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
 
195
        eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
 
196
        eq(t.abspath('.bzr/1//2/./3'),
 
197
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
 
198
 
 
199
    def test_invalid_http_urls(self):
 
200
        """Trap invalid construction of urls"""
 
201
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
 
202
        self.assertRaises(errors.InvalidURL,
 
203
                          self._transport,
 
204
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
 
205
 
 
206
    def test_http_root_urls(self):
 
207
        """Construction of URLs from server root"""
 
208
        t = self._transport('http://bzr.ozlabs.org/')
 
209
        eq = self.assertEqualDiff
 
210
        eq(t.abspath('.bzr/tree-version'),
 
211
           'http://bzr.ozlabs.org/.bzr/tree-version')
 
212
 
 
213
    def test_http_impl_urls(self):
 
214
        """There are servers which ask for particular clients to connect"""
 
215
        server = self._server()
 
216
        try:
 
217
            server.setUp()
 
218
            url = server.get_url()
 
219
            self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
 
220
        finally:
 
221
            server.tearDown()
 
222
 
 
223
 
 
224
class TestHttpUrls_urllib(TestHttpTransportUrls, tests.TestCase):
 
225
    """Test http urls with urllib"""
 
226
 
 
227
    _transport = HttpTransport_urllib
 
228
    _server = HttpServer_urllib
 
229
    _qualified_prefix = 'http+urllib'
 
230
 
 
231
 
 
232
class TestHttpUrls_pycurl(TestWithTransport_pycurl, TestHttpTransportUrls,
 
233
                          tests.TestCase):
 
234
    """Test http urls with pycurl"""
 
235
 
 
236
    _server = HttpServer_PyCurl
 
237
    _qualified_prefix = 'http+pycurl'
 
238
 
 
239
    # TODO: This should really be moved into another pycurl
 
240
    # specific test. When https tests will be implemented, take
 
241
    # this one into account.
 
242
    def test_pycurl_without_https_support(self):
 
243
        """Test that pycurl without SSL do not fail with a traceback.
 
244
 
 
245
        For the purpose of the test, we force pycurl to ignore
 
246
        https by supplying a fake version_info that do not
 
247
        support it.
 
248
        """
 
249
        try:
 
250
            import pycurl
 
251
        except ImportError:
 
252
            raise tests.TestSkipped('pycurl not present')
 
253
        # Now that we have pycurl imported, we can fake its version_info
 
254
        # This was taken from a windows pycurl without SSL
 
255
        # (thanks to bialix)
 
256
        pycurl.version_info = lambda : (2,
 
257
                                        '7.13.2',
 
258
                                        462082,
 
259
                                        'i386-pc-win32',
 
260
                                        2576,
 
261
                                        None,
 
262
                                        0,
 
263
                                        None,
 
264
                                        ('ftp', 'gopher', 'telnet',
 
265
                                         'dict', 'ldap', 'http', 'file'),
 
266
                                        None,
 
267
                                        0,
 
268
                                        None)
 
269
        self.assertRaises(errors.DependencyNotPresent, self._transport,
 
270
                          'https://launchpad.net')
 
271
 
 
272
class TestHttpConnections(object):
 
273
    """Test the http connections.
 
274
 
 
275
    This MUST be used by daughter classes that also inherit from
 
276
    TestCaseWithWebserver.
 
277
 
 
278
    We can't inherit directly from TestCaseWithWebserver or the
 
279
    test framework will try to create an instance which cannot
 
280
    run, its implementation being incomplete.
 
281
    """
 
282
 
 
283
    def setUp(self):
 
284
        TestCaseWithWebserver.setUp(self)
 
285
        self.build_tree(['xxx', 'foo/', 'foo/bar'], line_endings='binary',
 
286
                        transport=self.get_transport())
 
287
 
 
288
    def test_http_has(self):
 
289
        server = self.get_readonly_server()
 
290
        t = self._transport(server.get_url())
 
291
        self.assertEqual(t.has('foo/bar'), True)
 
292
        self.assertEqual(len(server.logs), 1)
 
293
        self.assertContainsRe(server.logs[0],
 
294
            r'"HEAD /foo/bar HTTP/1.." (200|302) - "-" "bzr/')
 
295
 
 
296
    def test_http_has_not_found(self):
 
297
        server = self.get_readonly_server()
 
298
        t = self._transport(server.get_url())
 
299
        self.assertEqual(t.has('not-found'), False)
 
300
        self.assertContainsRe(server.logs[1],
 
301
            r'"HEAD /not-found HTTP/1.." 404 - "-" "bzr/')
 
302
 
 
303
    def test_http_get(self):
 
304
        server = self.get_readonly_server()
 
305
        t = self._transport(server.get_url())
 
306
        fp = t.get('foo/bar')
 
307
        self.assertEqualDiff(
 
308
            fp.read(),
 
309
            'contents of foo/bar\n')
 
310
        self.assertEqual(len(server.logs), 1)
 
311
        self.assertTrue(server.logs[0].find(
 
312
            '"GET /foo/bar HTTP/1.1" 200 - "-" "bzr/%s'
 
313
            % bzrlib.__version__) > -1)
 
314
 
 
315
    def test_get_smart_medium(self):
 
316
        # For HTTP, get_smart_medium should return the transport object.
 
317
        server = self.get_readonly_server()
 
318
        http_transport = self._transport(server.get_url())
 
319
        medium = http_transport.get_smart_medium()
 
320
        self.assertIs(medium, http_transport)
 
321
 
 
322
    def test_has_on_bogus_host(self):
 
323
        # Get a free address and don't 'accept' on it, so that we
 
324
        # can be sure there is no http handler there, but set a
 
325
        # reasonable timeout to not slow down tests too much.
 
326
        default_timeout = socket.getdefaulttimeout()
 
327
        try:
 
328
            socket.setdefaulttimeout(2)
 
329
            s = socket.socket()
 
330
            s.bind(('localhost', 0))
 
331
            t = self._transport('http://%s:%s/' % s.getsockname())
 
332
            self.assertRaises(errors.ConnectionError, t.has, 'foo/bar')
 
333
        finally:
 
334
            socket.setdefaulttimeout(default_timeout)
 
335
 
 
336
 
 
337
class TestHttpConnections_urllib(TestHttpConnections, TestCaseWithWebserver):
 
338
    """Test http connections with urllib"""
 
339
 
 
340
    _transport = HttpTransport_urllib
 
341
 
 
342
 
 
343
 
 
344
class TestHttpConnections_pycurl(TestWithTransport_pycurl,
 
345
                                 TestHttpConnections,
 
346
                                 TestCaseWithWebserver):
 
347
    """Test http connections with pycurl"""
 
348
 
 
349
 
 
350
class TestHttpTransportRegistration(tests.TestCase):
 
351
    """Test registrations of various http implementations"""
 
352
 
 
353
    def test_http_registered(self):
 
354
        # urlllib should always be present
 
355
        t = get_transport('http+urllib://bzr.google.com/')
 
356
        self.assertIsInstance(t, Transport)
 
357
        self.assertIsInstance(t, HttpTransport_urllib)
 
358
 
 
359
 
 
360
class TestPost(object):
 
361
 
 
362
    def _test_post_body_is_received(self, scheme):
 
363
        server = RecordingServer(expect_body_tail='end-of-body')
 
364
        server.setUp()
 
365
        self.addCleanup(server.tearDown)
 
366
        url = '%s://%s:%s/' % (scheme, server.host, server.port)
 
367
        try:
 
368
            http_transport = get_transport(url)
 
369
        except errors.UnsupportedProtocol:
 
370
            raise tests.TestSkipped('%s not available' % scheme)
 
371
        code, response = http_transport._post('abc def end-of-body')
 
372
        self.assertTrue(
 
373
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
 
374
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
 
375
        # The transport should not be assuming that the server can accept
 
376
        # chunked encoding the first time it connects, because HTTP/1.1, so we
 
377
        # check for the literal string.
 
378
        self.assertTrue(
 
379
            server.received_bytes.endswith('\r\n\r\nabc def end-of-body'))
 
380
 
 
381
 
 
382
class TestPost_urllib(tests.TestCase, TestPost):
 
383
    """TestPost for urllib implementation"""
 
384
 
 
385
    _transport = HttpTransport_urllib
 
386
 
 
387
    def test_post_body_is_received_urllib(self):
 
388
        self._test_post_body_is_received('http+urllib')
 
389
 
 
390
 
 
391
class TestPost_pycurl(TestWithTransport_pycurl, tests.TestCase, TestPost):
 
392
    """TestPost for pycurl implementation"""
 
393
 
 
394
    def test_post_body_is_received_pycurl(self):
 
395
        self._test_post_body_is_received('http+pycurl')
 
396
 
 
397
 
 
398
class TestRangeHeader(tests.TestCase):
 
399
    """Test range_header method"""
 
400
 
 
401
    def check_header(self, value, ranges=[], tail=0):
 
402
        offsets = [ (start, end - start + 1) for start, end in ranges]
 
403
        coalesce = Transport._coalesce_offsets
 
404
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
 
405
        range_header = HttpTransportBase._range_header
 
406
        self.assertEqual(value, range_header(coalesced, tail))
 
407
 
 
408
    def test_range_header_single(self):
 
409
        self.check_header('0-9', ranges=[(0,9)])
 
410
        self.check_header('100-109', ranges=[(100,109)])
 
411
 
 
412
    def test_range_header_tail(self):
 
413
        self.check_header('-10', tail=10)
 
414
        self.check_header('-50', tail=50)
 
415
 
 
416
    def test_range_header_multi(self):
 
417
        self.check_header('0-9,100-200,300-5000',
 
418
                          ranges=[(0,9), (100, 200), (300,5000)])
 
419
 
 
420
    def test_range_header_mixed(self):
 
421
        self.check_header('0-9,300-5000,-50',
 
422
                          ranges=[(0,9), (300,5000)],
 
423
                          tail=50)
 
424
 
 
425
 
 
426
class TestWallServer(object):
 
427
    """Tests exceptions during the connection phase"""
 
428
 
 
429
    def create_transport_readonly_server(self):
 
430
        return HttpServer(WallRequestHandler)
 
431
 
 
432
    def test_http_has(self):
 
433
        server = self.get_readonly_server()
 
434
        t = self._transport(server.get_url())
 
435
        # Unfortunately httplib (see HTTPResponse._read_status
 
436
        # for details) make no distinction between a closed
 
437
        # socket and badly formatted status line, so we can't
 
438
        # just test for ConnectionError, we have to test
 
439
        # InvalidHttpResponse too.
 
440
        self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
 
441
                          t.has, 'foo/bar')
 
442
 
 
443
    def test_http_get(self):
 
444
        server = self.get_readonly_server()
 
445
        t = self._transport(server.get_url())
 
446
        self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
 
447
                          t.get, 'foo/bar')
 
448
 
 
449
 
 
450
class TestWallServer_urllib(TestWallServer, TestCaseWithWebserver):
 
451
    """Tests "wall" server for urllib implementation"""
 
452
 
 
453
    _transport = HttpTransport_urllib
 
454
 
 
455
 
 
456
class TestWallServer_pycurl(TestWithTransport_pycurl,
 
457
                            TestWallServer,
 
458
                            TestCaseWithWebserver):
 
459
    """Tests "wall" server for pycurl implementation"""
 
460
 
 
461
 
 
462
class TestBadStatusServer(object):
 
463
    """Tests bad status from server."""
 
464
 
 
465
    def create_transport_readonly_server(self):
 
466
        return HttpServer(BadStatusRequestHandler)
 
467
 
 
468
    def test_http_has(self):
 
469
        server = self.get_readonly_server()
 
470
        t = self._transport(server.get_url())
 
471
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
 
472
 
 
473
    def test_http_get(self):
 
474
        server = self.get_readonly_server()
 
475
        t = self._transport(server.get_url())
 
476
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
 
477
 
 
478
 
 
479
class TestBadStatusServer_urllib(TestBadStatusServer, TestCaseWithWebserver):
 
480
    """Tests bad status server for urllib implementation"""
 
481
 
 
482
    _transport = HttpTransport_urllib
 
483
 
 
484
 
 
485
class TestBadStatusServer_pycurl(TestWithTransport_pycurl,
 
486
                                 TestBadStatusServer,
 
487
                                 TestCaseWithWebserver):
 
488
    """Tests bad status server for pycurl implementation"""
 
489
 
 
490
 
 
491
class TestInvalidStatusServer(TestBadStatusServer):
 
492
    """Tests invalid status from server.
 
493
 
 
494
    Both implementations raises the same error as for a bad status.
 
495
    """
 
496
 
 
497
    def create_transport_readonly_server(self):
 
498
        return HttpServer(InvalidStatusRequestHandler)
 
499
 
 
500
 
 
501
class TestInvalidStatusServer_urllib(TestInvalidStatusServer,
 
502
                                     TestCaseWithWebserver):
 
503
    """Tests invalid status server for urllib implementation"""
 
504
 
 
505
    _transport = HttpTransport_urllib
 
506
 
 
507
 
 
508
class TestInvalidStatusServer_pycurl(TestWithTransport_pycurl,
 
509
                                     TestInvalidStatusServer,
 
510
                                     TestCaseWithWebserver):
 
511
    """Tests invalid status server for pycurl implementation"""
 
512
 
 
513
 
 
514
class TestBadProtocolServer(object):
 
515
    """Tests bad protocol from server."""
 
516
 
 
517
    def create_transport_readonly_server(self):
 
518
        return HttpServer(BadProtocolRequestHandler)
 
519
 
 
520
    def test_http_has(self):
 
521
        server = self.get_readonly_server()
 
522
        t = self._transport(server.get_url())
 
523
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
 
524
 
 
525
    def test_http_get(self):
 
526
        server = self.get_readonly_server()
 
527
        t = self._transport(server.get_url())
 
528
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
 
529
 
 
530
 
 
531
class TestBadProtocolServer_urllib(TestBadProtocolServer,
 
532
                                   TestCaseWithWebserver):
 
533
    """Tests bad protocol server for urllib implementation"""
 
534
 
 
535
    _transport = HttpTransport_urllib
 
536
 
 
537
# curl don't check the protocol version
 
538
#class TestBadProtocolServer_pycurl(TestWithTransport_pycurl,
 
539
#                                   TestBadProtocolServer,
 
540
#                                   TestCaseWithWebserver):
 
541
#    """Tests bad protocol server for pycurl implementation"""
 
542
 
 
543
 
 
544
class TestForbiddenServer(object):
 
545
    """Tests forbidden server"""
 
546
 
 
547
    def create_transport_readonly_server(self):
 
548
        return HttpServer(ForbiddenRequestHandler)
 
549
 
 
550
    def test_http_has(self):
 
551
        server = self.get_readonly_server()
 
552
        t = self._transport(server.get_url())
 
553
        self.assertRaises(errors.TransportError, t.has, 'foo/bar')
 
554
 
 
555
    def test_http_get(self):
 
556
        server = self.get_readonly_server()
 
557
        t = self._transport(server.get_url())
 
558
        self.assertRaises(errors.TransportError, t.get, 'foo/bar')
 
559
 
 
560
 
 
561
class TestForbiddenServer_urllib(TestForbiddenServer, TestCaseWithWebserver):
 
562
    """Tests forbidden server for urllib implementation"""
 
563
 
 
564
    _transport = HttpTransport_urllib
 
565
 
 
566
 
 
567
class TestForbiddenServer_pycurl(TestWithTransport_pycurl,
 
568
                                 TestForbiddenServer,
 
569
                                 TestCaseWithWebserver):
 
570
    """Tests forbidden server for pycurl implementation"""
 
571
 
 
572
 
 
573
class TestRecordingServer(tests.TestCase):
 
574
 
 
575
    def test_create(self):
 
576
        server = RecordingServer(expect_body_tail=None)
 
577
        self.assertEqual('', server.received_bytes)
 
578
        self.assertEqual(None, server.host)
 
579
        self.assertEqual(None, server.port)
 
580
 
 
581
    def test_setUp_and_tearDown(self):
 
582
        server = RecordingServer(expect_body_tail=None)
 
583
        server.setUp()
 
584
        try:
 
585
            self.assertNotEqual(None, server.host)
 
586
            self.assertNotEqual(None, server.port)
 
587
        finally:
 
588
            server.tearDown()
 
589
        self.assertEqual(None, server.host)
 
590
        self.assertEqual(None, server.port)
 
591
 
 
592
    def test_send_receive_bytes(self):
 
593
        server = RecordingServer(expect_body_tail='c')
 
594
        server.setUp()
 
595
        self.addCleanup(server.tearDown)
 
596
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
597
        sock.connect((server.host, server.port))
 
598
        sock.sendall('abc')
 
599
        self.assertEqual('HTTP/1.1 200 OK\r\n',
 
600
                         osutils.recv_all(sock, 4096))
 
601
        self.assertEqual('abc', server.received_bytes)
 
602
 
 
603
 
 
604
class TestRangeRequestServer(object):
 
605
    """Tests readv requests against server.
 
606
 
 
607
    This MUST be used by daughter classes that also inherit from
 
608
    TestCaseWithWebserver.
 
609
 
 
610
    We can't inherit directly from TestCaseWithWebserver or the
 
611
    test framework will try to create an instance which cannot
 
612
    run, its implementation being incomplete.
 
613
    """
 
614
 
 
615
    def setUp(self):
 
616
        TestCaseWithWebserver.setUp(self)
 
617
        self.build_tree_contents([('a', '0123456789')],)
 
618
 
 
619
    def test_readv(self):
 
620
        server = self.get_readonly_server()
 
621
        t = self._transport(server.get_url())
 
622
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
 
623
        self.assertEqual(l[0], (0, '0'))
 
624
        self.assertEqual(l[1], (1, '1'))
 
625
        self.assertEqual(l[2], (3, '34'))
 
626
        self.assertEqual(l[3], (9, '9'))
 
627
 
 
628
    def test_readv_out_of_order(self):
 
629
        server = self.get_readonly_server()
 
630
        t = self._transport(server.get_url())
 
631
        l = list(t.readv('a', ((1, 1), (9, 1), (0, 1), (3, 2))))
 
632
        self.assertEqual(l[0], (1, '1'))
 
633
        self.assertEqual(l[1], (9, '9'))
 
634
        self.assertEqual(l[2], (0, '0'))
 
635
        self.assertEqual(l[3], (3, '34'))
 
636
 
 
637
    def test_readv_invalid_ranges(self):
 
638
        server = self.get_readonly_server()
 
639
        t = self._transport(server.get_url())
 
640
 
 
641
        # This is intentionally reading off the end of the file
 
642
        # since we are sure that it cannot get there
 
643
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
 
644
                              t.readv, 'a', [(1,1), (8,10)])
 
645
 
 
646
        # This is trying to seek past the end of the file, it should
 
647
        # also raise a special error
 
648
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
 
649
                              t.readv, 'a', [(12,2)])
 
650
 
 
651
    def test_readv_multiple_get_requests(self):
 
652
        server = self.get_readonly_server()
 
653
        t = self._transport(server.get_url())
 
654
        # force transport to issue multiple requests
 
655
        t._max_readv_combine = 1
 
656
        t._max_get_ranges = 1
 
657
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
 
658
        self.assertEqual(l[0], (0, '0'))
 
659
        self.assertEqual(l[1], (1, '1'))
 
660
        self.assertEqual(l[2], (3, '34'))
 
661
        self.assertEqual(l[3], (9, '9'))
 
662
        # The server should have issued 4 requests
 
663
        self.assertEqual(4, self.get_readonly_server().GET_request_nb)
 
664
 
 
665
 
 
666
class TestSingleRangeRequestServer(TestRangeRequestServer):
 
667
    """Test readv against a server which accept only single range requests"""
 
668
 
 
669
    def create_transport_readonly_server(self):
 
670
        return HttpServer(SingleRangeRequestHandler)
 
671
 
 
672
 
 
673
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
 
674
                                          TestCaseWithWebserver):
 
675
    """Tests single range requests accepting server for urllib implementation"""
 
676
 
 
677
    _transport = HttpTransport_urllib
 
678
 
 
679
 
 
680
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
681
                                          TestSingleRangeRequestServer,
 
682
                                          TestCaseWithWebserver):
 
683
    """Tests single range requests accepting server for pycurl implementation"""
 
684
 
 
685
 
 
686
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
 
687
    """Test readv against a server which only accept single range requests"""
 
688
 
 
689
    def create_transport_readonly_server(self):
 
690
        return HttpServer(SingleOnlyRangeRequestHandler)
 
691
 
 
692
 
 
693
class TestSingleOnlyRangeRequestServer_urllib(TestSingleOnlyRangeRequestServer,
 
694
                                              TestCaseWithWebserver):
 
695
    """Tests single range requests accepting server for urllib implementation"""
 
696
 
 
697
    _transport = HttpTransport_urllib
 
698
 
 
699
 
 
700
class TestSingleOnlyRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
701
                                              TestSingleOnlyRangeRequestServer,
 
702
                                              TestCaseWithWebserver):
 
703
    """Tests single range requests accepting server for pycurl implementation"""
 
704
 
 
705
 
 
706
class TestNoRangeRequestServer(TestRangeRequestServer):
 
707
    """Test readv against a server which do not accept range requests"""
 
708
 
 
709
    def create_transport_readonly_server(self):
 
710
        return HttpServer(NoRangeRequestHandler)
 
711
 
 
712
 
 
713
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
 
714
                                      TestCaseWithWebserver):
 
715
    """Tests range requests refusing server for urllib implementation"""
 
716
 
 
717
    _transport = HttpTransport_urllib
 
718
 
 
719
 
 
720
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
721
                                      TestNoRangeRequestServer,
 
722
                                      TestCaseWithWebserver):
 
723
    """Tests range requests refusing server for pycurl implementation"""
 
724
 
 
725
 
 
726
class TestLimitedRangeRequestServer(object):
 
727
    """Tests readv requests against server that errors out on too much ranges.
 
728
 
 
729
    This MUST be used by daughter classes that also inherit from
 
730
    TestCaseWithWebserver.
 
731
 
 
732
    We can't inherit directly from TestCaseWithWebserver or the
 
733
    test framework will try to create an instance which cannot
 
734
    run, its implementation being incomplete.
 
735
    """
 
736
 
 
737
    range_limit = 3
 
738
 
 
739
    def create_transport_readonly_server(self):
 
740
        # Requests with more range specifiers will error out
 
741
        return LimitedRangeHTTPServer(range_limit=self.range_limit)
 
742
 
 
743
    def get_transport(self):
 
744
        return self._transport(self.get_readonly_server().get_url())
 
745
 
 
746
    def setUp(self):
 
747
        TestCaseWithWebserver.setUp(self)
 
748
        # We need to manipulate ranges that correspond to real chunks in the
 
749
        # response, so we build a content appropriately.
 
750
        filler = ''.join(['abcdefghij' for x in range(102)])
 
751
        content = ''.join(['%04d' % v + filler for v in range(16)])
 
752
        self.build_tree_contents([('a', content)],)
 
753
 
 
754
    def test_few_ranges(self):
 
755
        t = self.get_transport()
 
756
        l = list(t.readv('a', ((0, 4), (1024, 4), )))
 
757
        self.assertEqual(l[0], (0, '0000'))
 
758
        self.assertEqual(l[1], (1024, '0001'))
 
759
        self.assertEqual(1, self.get_readonly_server().GET_request_nb)
 
760
 
 
761
    def test_more_ranges(self):
 
762
        t = self.get_transport()
 
763
        l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
 
764
        self.assertEqual(l[0], (0, '0000'))
 
765
        self.assertEqual(l[1], (1024, '0001'))
 
766
        self.assertEqual(l[2], (4096, '0004'))
 
767
        self.assertEqual(l[3], (8192, '0008'))
 
768
        # The server will refuse to serve the first request (too much ranges),
 
769
        # a second request will succeeds.
 
770
        self.assertEqual(2, self.get_readonly_server().GET_request_nb)
 
771
 
 
772
 
 
773
class TestLimitedRangeRequestServer_urllib(TestLimitedRangeRequestServer,
 
774
                                          TestCaseWithWebserver):
 
775
    """Tests limited range requests server for urllib implementation"""
 
776
 
 
777
    _transport = HttpTransport_urllib
 
778
 
 
779
 
 
780
class TestLimitedRangeRequestServer_pycurl(TestWithTransport_pycurl,
 
781
                                          TestLimitedRangeRequestServer,
 
782
                                          TestCaseWithWebserver):
 
783
    """Tests limited range requests server for pycurl implementation"""
 
784
 
 
785
 
 
786
 
 
787
class TestHttpProxyWhiteBox(tests.TestCase):
 
788
    """Whitebox test proxy http authorization.
 
789
 
 
790
    Only the urllib implementation is tested here.
 
791
    """
 
792
 
 
793
    def setUp(self):
 
794
        tests.TestCase.setUp(self)
 
795
        self._old_env = {}
 
796
 
 
797
    def tearDown(self):
 
798
        self._restore_env()
 
799
 
 
800
    def _install_env(self, env):
 
801
        for name, value in env.iteritems():
 
802
            self._old_env[name] = osutils.set_or_unset_env(name, value)
 
803
 
 
804
    def _restore_env(self):
 
805
        for name, value in self._old_env.iteritems():
 
806
            osutils.set_or_unset_env(name, value)
 
807
 
 
808
    def _proxied_request(self):
 
809
        handler = ProxyHandler()
 
810
        request = Request('GET','http://baz/buzzle')
 
811
        handler.set_proxy(request, 'http')
 
812
        return request
 
813
 
 
814
    def test_empty_user(self):
 
815
        self._install_env({'http_proxy': 'http://bar.com'})
 
816
        request = self._proxied_request()
 
817
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
 
818
 
 
819
    def test_invalid_proxy(self):
 
820
        """A proxy env variable without scheme"""
 
821
        self._install_env({'http_proxy': 'host:1234'})
 
822
        self.assertRaises(errors.InvalidURL, self._proxied_request)
 
823
 
 
824
 
 
825
class TestProxyHttpServer(object):
 
826
    """Tests proxy server.
 
827
 
 
828
    This MUST be used by daughter classes that also inherit from
 
829
    TestCaseWithTwoWebservers.
 
830
 
 
831
    We can't inherit directly from TestCaseWithTwoWebservers or
 
832
    the test framework will try to create an instance which
 
833
    cannot run, its implementation being incomplete.
 
834
 
 
835
    Be aware that we do not setup a real proxy here. Instead, we
 
836
    check that the *connection* goes through the proxy by serving
 
837
    different content (the faked proxy server append '-proxied'
 
838
    to the file names).
 
839
    """
 
840
 
 
841
    # FIXME: We don't have an https server available, so we don't
 
842
    # test https connections.
 
843
 
 
844
    def setUp(self):
 
845
        TestCaseWithTwoWebservers.setUp(self)
 
846
        self.build_tree_contents([('foo', 'contents of foo\n'),
 
847
                                  ('foo-proxied', 'proxied contents of foo\n')])
 
848
        # Let's setup some attributes for tests
 
849
        self.server = self.get_readonly_server()
 
850
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
 
851
        self.no_proxy_host = self.proxy_address
 
852
        # The secondary server is the proxy
 
853
        self.proxy = self.get_secondary_server()
 
854
        self.proxy_url = self.proxy.get_url()
 
855
        self._old_env = {}
 
856
 
 
857
    def create_transport_secondary_server(self):
 
858
        """Creates an http server that will serve files with
 
859
        '-proxied' appended to their names.
 
860
        """
 
861
        return ProxyServer()
 
862
 
 
863
    def _install_env(self, env):
 
864
        for name, value in env.iteritems():
 
865
            self._old_env[name] = osutils.set_or_unset_env(name, value)
 
866
 
 
867
    def _restore_env(self):
 
868
        for name, value in self._old_env.iteritems():
 
869
            osutils.set_or_unset_env(name, value)
 
870
 
 
871
    def proxied_in_env(self, env):
 
872
        self._install_env(env)
 
873
        url = self.server.get_url()
 
874
        t = self._transport(url)
 
875
        try:
 
876
            self.assertEqual(t.get('foo').read(), 'proxied contents of foo\n')
 
877
        finally:
 
878
            self._restore_env()
 
879
 
 
880
    def not_proxied_in_env(self, env):
 
881
        self._install_env(env)
 
882
        url = self.server.get_url()
 
883
        t = self._transport(url)
 
884
        try:
 
885
            self.assertEqual(t.get('foo').read(), 'contents of foo\n')
 
886
        finally:
 
887
            self._restore_env()
 
888
 
 
889
    def test_http_proxy(self):
 
890
        self.proxied_in_env({'http_proxy': self.proxy_url})
 
891
 
 
892
    def test_HTTP_PROXY(self):
 
893
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
 
894
 
 
895
    def test_all_proxy(self):
 
896
        self.proxied_in_env({'all_proxy': self.proxy_url})
 
897
 
 
898
    def test_ALL_PROXY(self):
 
899
        self.proxied_in_env({'ALL_PROXY': self.proxy_url})
 
900
 
 
901
    def test_http_proxy_with_no_proxy(self):
 
902
        self.not_proxied_in_env({'http_proxy': self.proxy_url,
 
903
                                 'no_proxy': self.no_proxy_host})
 
904
 
 
905
    def test_HTTP_PROXY_with_NO_PROXY(self):
 
906
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
 
907
                                 'NO_PROXY': self.no_proxy_host})
 
908
 
 
909
    def test_all_proxy_with_no_proxy(self):
 
910
        self.not_proxied_in_env({'all_proxy': self.proxy_url,
 
911
                                 'no_proxy': self.no_proxy_host})
 
912
 
 
913
    def test_ALL_PROXY_with_NO_PROXY(self):
 
914
        self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
 
915
                                 'NO_PROXY': self.no_proxy_host})
 
916
 
 
917
    def test_http_proxy_without_scheme(self):
 
918
        self.assertRaises(errors.InvalidURL,
 
919
                          self.proxied_in_env,
 
920
                          {'http_proxy': self.proxy_address})
 
921
 
 
922
 
 
923
class TestProxyHttpServer_urllib(TestProxyHttpServer,
 
924
                                 TestCaseWithTwoWebservers):
 
925
    """Tests proxy server for urllib implementation"""
 
926
 
 
927
    _transport = HttpTransport_urllib
 
928
 
 
929
 
 
930
class TestProxyHttpServer_pycurl(TestWithTransport_pycurl,
 
931
                                 TestProxyHttpServer,
 
932
                                 TestCaseWithTwoWebservers):
 
933
    """Tests proxy server for pycurl implementation"""
 
934
 
 
935
    def setUp(self):
 
936
        TestProxyHttpServer.setUp(self)
 
937
        # Oh my ! pycurl does not check for the port as part of
 
938
        # no_proxy :-( So we just test the host part
 
939
        self.no_proxy_host = 'localhost'
 
940
 
 
941
    def test_HTTP_PROXY(self):
 
942
        # pycurl does not check HTTP_PROXY for security reasons
 
943
        # (for use in a CGI context that we do not care
 
944
        # about. Should we ?)
 
945
        raise tests.TestNotApplicable(
 
946
            'pycurl does not check HTTP_PROXY for security reasons')
 
947
 
 
948
    def test_HTTP_PROXY_with_NO_PROXY(self):
 
949
        raise tests.TestNotApplicable(
 
950
            'pycurl does not check HTTP_PROXY for security reasons')
 
951
 
 
952
    def test_http_proxy_without_scheme(self):
 
953
        # pycurl *ignores* invalid proxy env variables. If that
 
954
        # ever change in the future, this test will fail
 
955
        # indicating that pycurl do not ignore anymore such
 
956
        # variables.
 
957
        self.not_proxied_in_env({'http_proxy': self.proxy_address})
 
958
 
 
959
 
 
960
class TestRanges(object):
 
961
    """Test the Range header in GET methods..
 
962
 
 
963
    This MUST be used by daughter classes that also inherit from
 
964
    TestCaseWithWebserver.
 
965
 
 
966
    We can't inherit directly from TestCaseWithWebserver or the
 
967
    test framework will try to create an instance which cannot
 
968
    run, its implementation being incomplete.
 
969
    """
 
970
 
 
971
    def setUp(self):
 
972
        TestCaseWithWebserver.setUp(self)
 
973
        self.build_tree_contents([('a', '0123456789')],)
 
974
        server = self.get_readonly_server()
 
975
        self.transport = self._transport(server.get_url())
 
976
 
 
977
    def _file_contents(self, relpath, ranges):
 
978
        offsets = [ (start, end - start + 1) for start, end in ranges]
 
979
        coalesce = self.transport._coalesce_offsets
 
980
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
 
981
        code, data = self.transport._get(relpath, coalesced)
 
982
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
 
983
        for start, end in ranges:
 
984
            data.seek(start)
 
985
            yield data.read(end - start + 1)
 
986
 
 
987
    def _file_tail(self, relpath, tail_amount):
 
988
        code, data = self.transport._get(relpath, [], tail_amount)
 
989
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
 
990
        data.seek(-tail_amount + 1, 2)
 
991
        return data.read(tail_amount)
 
992
 
 
993
    def test_range_header(self):
 
994
        # Valid ranges
 
995
        map(self.assertEqual,['0', '234'],
 
996
            list(self._file_contents('a', [(0,0), (2,4)])),)
 
997
        # Tail
 
998
        self.assertEqual('789', self._file_tail('a', 3))
 
999
        # Syntactically invalid range
 
1000
        self.assertListRaises(errors.InvalidRange,
 
1001
                          self._file_contents, 'a', [(4, 3)])
 
1002
        # Semantically invalid range
 
1003
        self.assertListRaises(errors.InvalidRange,
 
1004
                          self._file_contents, 'a', [(42, 128)])
 
1005
 
 
1006
 
 
1007
class TestRanges_urllib(TestRanges, TestCaseWithWebserver):
 
1008
    """Test the Range header in GET methods for urllib implementation"""
 
1009
 
 
1010
    _transport = HttpTransport_urllib
 
1011
 
 
1012
 
 
1013
class TestRanges_pycurl(TestWithTransport_pycurl,
 
1014
                        TestRanges,
 
1015
                        TestCaseWithWebserver):
 
1016
    """Test the Range header in GET methods for pycurl implementation"""
 
1017
 
 
1018
 
 
1019
class TestHTTPRedirections(object):
 
1020
    """Test redirection between http servers.
 
1021
 
 
1022
    This MUST be used by daughter classes that also inherit from
 
1023
    TestCaseWithRedirectedWebserver.
 
1024
 
 
1025
    We can't inherit directly from TestCaseWithTwoWebservers or the
 
1026
    test framework will try to create an instance which cannot
 
1027
    run, its implementation being incomplete. 
 
1028
    """
 
1029
 
 
1030
    def create_transport_secondary_server(self):
 
1031
        """Create the secondary server redirecting to the primary server"""
 
1032
        new = self.get_readonly_server()
 
1033
 
 
1034
        redirecting = HTTPServerRedirecting()
 
1035
        redirecting.redirect_to(new.host, new.port)
 
1036
        return redirecting
 
1037
 
 
1038
    def setUp(self):
 
1039
        super(TestHTTPRedirections, self).setUp()
 
1040
        self.build_tree_contents([('a', '0123456789'),
 
1041
                                  ('bundle',
 
1042
                                  '# Bazaar revision bundle v0.9\n#\n')
 
1043
                                  ],)
 
1044
 
 
1045
        self.old_transport = self._transport(self.old_server.get_url())
 
1046
 
 
1047
    def test_redirected(self):
 
1048
        self.assertRaises(errors.RedirectRequested, self.old_transport.get, 'a')
 
1049
        t = self._transport(self.new_server.get_url())
 
1050
        self.assertEqual('0123456789', t.get('a').read())
 
1051
 
 
1052
    def test_read_redirected_bundle_from_url(self):
 
1053
        from bzrlib.bundle import read_bundle_from_url
 
1054
        url = self.old_transport.abspath('bundle')
 
1055
        bundle = read_bundle_from_url(url)
 
1056
        # If read_bundle_from_url was successful we get an empty bundle
 
1057
        self.assertEqual([], bundle.revisions)
 
1058
 
 
1059
 
 
1060
class TestHTTPRedirections_urllib(TestHTTPRedirections,
 
1061
                                  TestCaseWithRedirectedWebserver):
 
1062
    """Tests redirections for urllib implementation"""
 
1063
 
 
1064
    _transport = HttpTransport_urllib
 
1065
 
 
1066
 
 
1067
 
 
1068
class TestHTTPRedirections_pycurl(TestWithTransport_pycurl,
 
1069
                                  TestHTTPRedirections,
 
1070
                                  TestCaseWithRedirectedWebserver):
 
1071
    """Tests redirections for pycurl implementation"""
 
1072
 
 
1073
 
 
1074
class RedirectedRequest(Request):
 
1075
    """Request following redirections"""
 
1076
 
 
1077
    init_orig = Request.__init__
 
1078
 
 
1079
    def __init__(self, method, url, *args, **kwargs):
 
1080
        RedirectedRequest.init_orig(self, method, url, args, kwargs)
 
1081
        self.follow_redirections = True
 
1082
 
 
1083
 
 
1084
class TestHTTPSilentRedirections_urllib(TestCaseWithRedirectedWebserver):
 
1085
    """Test redirections provided by urllib.
 
1086
 
 
1087
    http implementations do not redirect silently anymore (they
 
1088
    do not redirect at all in fact). The mechanism is still in
 
1089
    place at the _urllib2_wrappers.Request level and these tests
 
1090
    exercise it.
 
1091
 
 
1092
    For the pycurl implementation
 
1093
    the redirection have been deleted as we may deprecate pycurl
 
1094
    and I have no place to keep a working implementation.
 
1095
    -- vila 20070212
 
1096
    """
 
1097
 
 
1098
    _transport = HttpTransport_urllib
 
1099
 
 
1100
    def setUp(self):
 
1101
        super(TestHTTPSilentRedirections_urllib, self).setUp()
 
1102
        self.setup_redirected_request()
 
1103
        self.addCleanup(self.cleanup_redirected_request)
 
1104
        self.build_tree_contents([('a','a'),
 
1105
                                  ('1/',),
 
1106
                                  ('1/a', 'redirected once'),
 
1107
                                  ('2/',),
 
1108
                                  ('2/a', 'redirected twice'),
 
1109
                                  ('3/',),
 
1110
                                  ('3/a', 'redirected thrice'),
 
1111
                                  ('4/',),
 
1112
                                  ('4/a', 'redirected 4 times'),
 
1113
                                  ('5/',),
 
1114
                                  ('5/a', 'redirected 5 times'),
 
1115
                                  ],)
 
1116
 
 
1117
        self.old_transport = self._transport(self.old_server.get_url())
 
1118
 
 
1119
    def setup_redirected_request(self):
 
1120
        self.original_class = _urllib2_wrappers.Request
 
1121
        _urllib2_wrappers.Request = RedirectedRequest
 
1122
 
 
1123
    def cleanup_redirected_request(self):
 
1124
        _urllib2_wrappers.Request = self.original_class
 
1125
 
 
1126
    def create_transport_secondary_server(self):
 
1127
        """Create the secondary server, redirections are defined in the tests"""
 
1128
        return HTTPServerRedirecting()
 
1129
 
 
1130
    def test_one_redirection(self):
 
1131
        t = self.old_transport
 
1132
 
 
1133
        req = RedirectedRequest('GET', t.abspath('a'))
 
1134
        req.follow_redirections = True
 
1135
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
1136
                                       self.new_server.port)
 
1137
        self.old_server.redirections = \
 
1138
            [('(.*)', r'%s/1\1' % (new_prefix), 301),]
 
1139
        self.assertEquals('redirected once',t._perform(req).read())
 
1140
 
 
1141
    def test_five_redirections(self):
 
1142
        t = self.old_transport
 
1143
 
 
1144
        req = RedirectedRequest('GET', t.abspath('a'))
 
1145
        req.follow_redirections = True
 
1146
        old_prefix = 'http://%s:%s' % (self.old_server.host,
 
1147
                                       self.old_server.port)
 
1148
        new_prefix = 'http://%s:%s' % (self.new_server.host,
 
1149
                                       self.new_server.port)
 
1150
        self.old_server.redirections = \
 
1151
            [('/1(.*)', r'%s/2\1' % (old_prefix), 302),
 
1152
             ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
 
1153
             ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
 
1154
             ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
 
1155
             ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
 
1156
             ]
 
1157
        self.assertEquals('redirected 5 times',t._perform(req).read())
 
1158
 
 
1159
 
 
1160
class TestDoCatchRedirections(TestCaseWithRedirectedWebserver):
 
1161
    """Test transport.do_catching_redirections.
 
1162
 
 
1163
    We arbitrarily choose to use urllib transports
 
1164
    """
 
1165
 
 
1166
    _transport = HttpTransport_urllib
 
1167
 
 
1168
    def setUp(self):
 
1169
        super(TestDoCatchRedirections, self).setUp()
 
1170
        self.build_tree_contents([('a', '0123456789'),],)
 
1171
 
 
1172
        self.old_transport = self._transport(self.old_server.get_url())
 
1173
 
 
1174
    def get_a(self, transport):
 
1175
        return transport.get('a')
 
1176
 
 
1177
    def test_no_redirection(self):
 
1178
        t = self._transport(self.new_server.get_url())
 
1179
 
 
1180
        # We use None for redirected so that we fail if redirected
 
1181
        self.assertEquals('0123456789',
 
1182
                          do_catching_redirections(self.get_a, t, None).read())
 
1183
 
 
1184
    def test_one_redirection(self):
 
1185
        self.redirections = 0
 
1186
 
 
1187
        def redirected(transport, exception, redirection_notice):
 
1188
            self.redirections += 1
 
1189
            dir, file = urlutils.split(exception.target)
 
1190
            return self._transport(dir)
 
1191
 
 
1192
        self.assertEquals('0123456789',
 
1193
                          do_catching_redirections(self.get_a,
 
1194
                                                   self.old_transport,
 
1195
                                                   redirected
 
1196
                                                   ).read())
 
1197
        self.assertEquals(1, self.redirections)
 
1198
 
 
1199
    def test_redirection_loop(self):
 
1200
 
 
1201
        def redirected(transport, exception, redirection_notice):
 
1202
            # By using the redirected url as a base dir for the
 
1203
            # *old* transport, we create a loop: a => a/a =>
 
1204
            # a/a/a
 
1205
            return self.old_transport.clone(exception.target)
 
1206
 
 
1207
        self.assertRaises(errors.TooManyRedirections, do_catching_redirections,
 
1208
                          self.get_a, self.old_transport, redirected)
 
1209
 
 
1210
 
 
1211
class TestAuth(object):
 
1212
    """Test some authentication scheme specified by daughter class.
 
1213
 
 
1214
    This MUST be used by daughter classes that also inherit from
 
1215
    either TestCaseWithWebserver or TestCaseWithTwoWebservers.
 
1216
    """
 
1217
 
 
1218
    _password_prompt_prefix = ''
 
1219
 
 
1220
    def setUp(self):
 
1221
        """Set up the test environment
 
1222
 
 
1223
        Daughter classes should set up their own environment
 
1224
        (including self.server) and explicitely call this
 
1225
        method. This is needed because we want to reuse the same
 
1226
        tests for proxy and no-proxy accesses which have
 
1227
        different ways of setting self.server.
 
1228
        """
 
1229
        self.build_tree_contents([('a', 'contents of a\n'),
 
1230
                                  ('b', 'contents of b\n'),])
 
1231
 
 
1232
    def get_user_url(self, user=None, password=None):
 
1233
        """Build an url embedding user and password"""
 
1234
        url = '%s://' % self.server._url_protocol
 
1235
        if user is not None:
 
1236
            url += user
 
1237
            if password is not None:
 
1238
                url += ':' + password
 
1239
            url += '@'
 
1240
        url += '%s:%s/' % (self.server.host, self.server.port)
 
1241
        return url
 
1242
 
 
1243
    def test_no_user(self):
 
1244
        self.server.add_user('joe', 'foo')
 
1245
        t = self.get_user_transport()
 
1246
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
 
1247
        # Only one 'Authentication Required' error should occur
 
1248
        self.assertEqual(1, self.server.auth_required_errors)
 
1249
 
 
1250
    def test_empty_pass(self):
 
1251
        self.server.add_user('joe', '')
 
1252
        t = self.get_user_transport('joe', '')
 
1253
        self.assertEqual('contents of a\n', t.get('a').read())
 
1254
        # Only one 'Authentication Required' error should occur
 
1255
        self.assertEqual(1, self.server.auth_required_errors)
 
1256
 
 
1257
    def test_user_pass(self):
 
1258
        self.server.add_user('joe', 'foo')
 
1259
        t = self.get_user_transport('joe', 'foo')
 
1260
        self.assertEqual('contents of a\n', t.get('a').read())
 
1261
        # Only one 'Authentication Required' error should occur
 
1262
        self.assertEqual(1, self.server.auth_required_errors)
 
1263
 
 
1264
    def test_unknown_user(self):
 
1265
        self.server.add_user('joe', 'foo')
 
1266
        t = self.get_user_transport('bill', 'foo')
 
1267
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
 
1268
        # Two 'Authentication Required' errors should occur (the
 
1269
        # initial 'who are you' and 'I don't know you, who are
 
1270
        # you').
 
1271
        self.assertEqual(2, self.server.auth_required_errors)
 
1272
 
 
1273
    def test_wrong_pass(self):
 
1274
        self.server.add_user('joe', 'foo')
 
1275
        t = self.get_user_transport('joe', 'bar')
 
1276
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
 
1277
        # Two 'Authentication Required' errors should occur (the
 
1278
        # initial 'who are you' and 'this is not you, who are you')
 
1279
        self.assertEqual(2, self.server.auth_required_errors)
 
1280
 
 
1281
    def test_prompt_for_password(self):
 
1282
        self.server.add_user('joe', 'foo')
 
1283
        t = self.get_user_transport('joe', None)
 
1284
        stdout = tests.StringIOWrapper()
 
1285
        ui.ui_factory = tests.TestUIFactory(stdin='foo\n', stdout=stdout)
 
1286
        self.assertEqual('contents of a\n',t.get('a').read())
 
1287
        # stdin should be empty
 
1288
        self.assertEqual('', ui.ui_factory.stdin.readline())
 
1289
        self._check_password_prompt(t._unqualified_scheme, 'joe',
 
1290
                                    stdout.getvalue())
 
1291
        # And we shouldn't prompt again for a different request
 
1292
        # against the same transport.
 
1293
        self.assertEqual('contents of b\n',t.get('b').read())
 
1294
        t2 = t.clone()
 
1295
        # And neither against a clone
 
1296
        self.assertEqual('contents of b\n',t2.get('b').read())
 
1297
        # Only one 'Authentication Required' error should occur
 
1298
        self.assertEqual(1, self.server.auth_required_errors)
 
1299
 
 
1300
    def _check_password_prompt(self, scheme, user, actual_prompt):
 
1301
        expected_prompt = (self._password_prompt_prefix
 
1302
                           + ("%s %s@%s:%d, Realm: '%s' password: "
 
1303
                              % (scheme.upper(),
 
1304
                                 user, self.server.host, self.server.port,
 
1305
                                 self.server.auth_realm)))
 
1306
        self.assertEquals(expected_prompt, actual_prompt)
 
1307
 
 
1308
    def test_no_prompt_for_password_when_using_auth_config(self):
 
1309
        user =' joe'
 
1310
        password = 'foo'
 
1311
        stdin_content = 'bar\n'  # Not the right password
 
1312
        self.server.add_user(user, password)
 
1313
        t = self.get_user_transport(user, None)
 
1314
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
 
1315
                                            stdout=tests.StringIOWrapper())
 
1316
        # Create a minimal config file with the right password
 
1317
        conf = config.AuthenticationConfig()
 
1318
        conf._get_config().update(
 
1319
            {'httptest': {'scheme': 'http', 'port': self.server.port,
 
1320
                          'user': user, 'password': password}})
 
1321
        conf._save()
 
1322
        # Issue a request to the server to connect
 
1323
        self.assertEqual('contents of a\n',t.get('a').read())
 
1324
        # stdin should have  been left untouched
 
1325
        self.assertEqual(stdin_content, ui.ui_factory.stdin.readline())
 
1326
        # Only one 'Authentication Required' error should occur
 
1327
        self.assertEqual(1, self.server.auth_required_errors)
 
1328
 
 
1329
 
 
1330
 
 
1331
class TestHTTPAuth(TestAuth):
 
1332
    """Test HTTP authentication schemes.
 
1333
 
 
1334
    Daughter classes MUST inherit from TestCaseWithWebserver too.
 
1335
    """
 
1336
 
 
1337
    _auth_header = 'Authorization'
 
1338
 
 
1339
    def setUp(self):
 
1340
        TestCaseWithWebserver.setUp(self)
 
1341
        self.server = self.get_readonly_server()
 
1342
        TestAuth.setUp(self)
 
1343
 
 
1344
    def get_user_transport(self, user=None, password=None):
 
1345
        return self._transport(self.get_user_url(user, password))
 
1346
 
 
1347
 
 
1348
class TestProxyAuth(TestAuth):
 
1349
    """Test proxy authentication schemes.
 
1350
 
 
1351
    Daughter classes MUST also inherit from TestCaseWithWebserver.
 
1352
    """
 
1353
    _auth_header = 'Proxy-authorization'
 
1354
    _password_prompt_prefix = 'Proxy '
 
1355
 
 
1356
 
 
1357
    def setUp(self):
 
1358
        TestCaseWithWebserver.setUp(self)
 
1359
        self.server = self.get_readonly_server()
 
1360
        self._old_env = {}
 
1361
        self.addCleanup(self._restore_env)
 
1362
        TestAuth.setUp(self)
 
1363
        # Override the contents to avoid false positives
 
1364
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
 
1365
                                  ('b', 'not proxied contents of b\n'),
 
1366
                                  ('a-proxied', 'contents of a\n'),
 
1367
                                  ('b-proxied', 'contents of b\n'),
 
1368
                                  ])
 
1369
 
 
1370
    def get_user_transport(self, user=None, password=None):
 
1371
        self._install_env({'all_proxy': self.get_user_url(user, password)})
 
1372
        return self._transport(self.server.get_url())
 
1373
 
 
1374
    def _install_env(self, env):
 
1375
        for name, value in env.iteritems():
 
1376
            self._old_env[name] = osutils.set_or_unset_env(name, value)
 
1377
 
 
1378
    def _restore_env(self):
 
1379
        for name, value in self._old_env.iteritems():
 
1380
            osutils.set_or_unset_env(name, value)
 
1381
 
 
1382
 
 
1383
class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
 
1384
    """Test http basic authentication scheme"""
 
1385
 
 
1386
    _transport = HttpTransport_urllib
 
1387
 
 
1388
    def create_transport_readonly_server(self):
 
1389
        return HTTPBasicAuthServer()
 
1390
 
 
1391
 
 
1392
class TestHTTPProxyBasicAuth(TestProxyAuth, TestCaseWithWebserver):
 
1393
    """Test proxy basic authentication scheme"""
 
1394
 
 
1395
    _transport = HttpTransport_urllib
 
1396
 
 
1397
    def create_transport_readonly_server(self):
 
1398
        return ProxyBasicAuthServer()
 
1399
 
 
1400
 
 
1401
class TestDigestAuth(object):
 
1402
    """Digest Authentication specific tests"""
 
1403
 
 
1404
    def test_changing_nonce(self):
 
1405
        self.server.add_user('joe', 'foo')
 
1406
        t = self.get_user_transport('joe', 'foo')
 
1407
        self.assertEqual('contents of a\n', t.get('a').read())
 
1408
        self.assertEqual('contents of b\n', t.get('b').read())
 
1409
        # Only one 'Authentication Required' error should have
 
1410
        # occured so far
 
1411
        self.assertEqual(1, self.server.auth_required_errors)
 
1412
        # The server invalidates the current nonce
 
1413
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
 
1414
        self.assertEqual('contents of a\n', t.get('a').read())
 
1415
        # Two 'Authentication Required' errors should occur (the
 
1416
        # initial 'who are you' and a second 'who are you' with the new nonce)
 
1417
        self.assertEqual(2, self.server.auth_required_errors)
 
1418
 
 
1419
 
 
1420
class TestHTTPDigestAuth(TestHTTPAuth, TestDigestAuth, TestCaseWithWebserver):
 
1421
    """Test http digest authentication scheme"""
 
1422
 
 
1423
    _transport = HttpTransport_urllib
 
1424
 
 
1425
    def create_transport_readonly_server(self):
 
1426
        return HTTPDigestAuthServer()
 
1427
 
 
1428
 
 
1429
class TestHTTPProxyDigestAuth(TestProxyAuth, TestDigestAuth,
 
1430
                              TestCaseWithWebserver):
 
1431
    """Test proxy digest authentication scheme"""
 
1432
 
 
1433
    _transport = HttpTransport_urllib
 
1434
 
 
1435
    def create_transport_readonly_server(self):
 
1436
        return ProxyDigestAuthServer()
 
1437