/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

More work on roundtrip push support.

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, server.GET_request_nb)
664
 
 
665
 
    def test_readv_get_max_size(self):
666
 
        server = self.get_readonly_server()
667
 
        t = self._transport(server.get_url())
668
 
        # force transport to issue multiple requests by limiting the number of
669
 
        # bytes by request. Note that this apply to coalesced offsets only, a
670
 
        # single range ill keep its size even if bigger than the limit.
671
 
        t._get_max_size = 2
672
 
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
673
 
        self.assertEqual(l[0], (0, '0'))
674
 
        self.assertEqual(l[1], (1, '1'))
675
 
        self.assertEqual(l[2], (2, '2345'))
676
 
        self.assertEqual(l[3], (6, '6789'))
677
 
        # The server should have issued 3 requests
678
 
        self.assertEqual(3, server.GET_request_nb)
679
 
 
680
 
 
681
 
class TestSingleRangeRequestServer(TestRangeRequestServer):
682
 
    """Test readv against a server which accept only single range requests"""
683
 
 
684
 
    def create_transport_readonly_server(self):
685
 
        return HttpServer(SingleRangeRequestHandler)
686
 
 
687
 
 
688
 
class TestSingleRangeRequestServer_urllib(TestSingleRangeRequestServer,
689
 
                                          TestCaseWithWebserver):
690
 
    """Tests single range requests accepting server for urllib implementation"""
691
 
 
692
 
    _transport = HttpTransport_urllib
693
 
 
694
 
 
695
 
class TestSingleRangeRequestServer_pycurl(TestWithTransport_pycurl,
696
 
                                          TestSingleRangeRequestServer,
697
 
                                          TestCaseWithWebserver):
698
 
    """Tests single range requests accepting server for pycurl implementation"""
699
 
 
700
 
 
701
 
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
702
 
    """Test readv against a server which only accept single range requests"""
703
 
 
704
 
    def create_transport_readonly_server(self):
705
 
        return HttpServer(SingleOnlyRangeRequestHandler)
706
 
 
707
 
 
708
 
class TestSingleOnlyRangeRequestServer_urllib(TestSingleOnlyRangeRequestServer,
709
 
                                              TestCaseWithWebserver):
710
 
    """Tests single range requests accepting server for urllib implementation"""
711
 
 
712
 
    _transport = HttpTransport_urllib
713
 
 
714
 
 
715
 
class TestSingleOnlyRangeRequestServer_pycurl(TestWithTransport_pycurl,
716
 
                                              TestSingleOnlyRangeRequestServer,
717
 
                                              TestCaseWithWebserver):
718
 
    """Tests single range requests accepting server for pycurl implementation"""
719
 
 
720
 
 
721
 
class TestNoRangeRequestServer(TestRangeRequestServer):
722
 
    """Test readv against a server which do not accept range requests"""
723
 
 
724
 
    def create_transport_readonly_server(self):
725
 
        return HttpServer(NoRangeRequestHandler)
726
 
 
727
 
 
728
 
class TestNoRangeRequestServer_urllib(TestNoRangeRequestServer,
729
 
                                      TestCaseWithWebserver):
730
 
    """Tests range requests refusing server for urllib implementation"""
731
 
 
732
 
    _transport = HttpTransport_urllib
733
 
 
734
 
 
735
 
class TestNoRangeRequestServer_pycurl(TestWithTransport_pycurl,
736
 
                                      TestNoRangeRequestServer,
737
 
                                      TestCaseWithWebserver):
738
 
    """Tests range requests refusing server for pycurl implementation"""
739
 
 
740
 
 
741
 
class TestLimitedRangeRequestServer(object):
742
 
    """Tests readv requests against server that errors out on too much ranges.
743
 
 
744
 
    This MUST be used by daughter classes that also inherit from
745
 
    TestCaseWithWebserver.
746
 
 
747
 
    We can't inherit directly from TestCaseWithWebserver or the
748
 
    test framework will try to create an instance which cannot
749
 
    run, its implementation being incomplete.
750
 
    """
751
 
 
752
 
    range_limit = 3
753
 
 
754
 
    def create_transport_readonly_server(self):
755
 
        # Requests with more range specifiers will error out
756
 
        return LimitedRangeHTTPServer(range_limit=self.range_limit)
757
 
 
758
 
    def get_transport(self):
759
 
        return self._transport(self.get_readonly_server().get_url())
760
 
 
761
 
    def setUp(self):
762
 
        TestCaseWithWebserver.setUp(self)
763
 
        # We need to manipulate ranges that correspond to real chunks in the
764
 
        # response, so we build a content appropriately.
765
 
        filler = ''.join(['abcdefghij' for x in range(102)])
766
 
        content = ''.join(['%04d' % v + filler for v in range(16)])
767
 
        self.build_tree_contents([('a', content)],)
768
 
 
769
 
    def test_few_ranges(self):
770
 
        t = self.get_transport()
771
 
        l = list(t.readv('a', ((0, 4), (1024, 4), )))
772
 
        self.assertEqual(l[0], (0, '0000'))
773
 
        self.assertEqual(l[1], (1024, '0001'))
774
 
        self.assertEqual(1, self.get_readonly_server().GET_request_nb)
775
 
 
776
 
    def test_more_ranges(self):
777
 
        t = self.get_transport()
778
 
        l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
779
 
        self.assertEqual(l[0], (0, '0000'))
780
 
        self.assertEqual(l[1], (1024, '0001'))
781
 
        self.assertEqual(l[2], (4096, '0004'))
782
 
        self.assertEqual(l[3], (8192, '0008'))
783
 
        # The server will refuse to serve the first request (too much ranges),
784
 
        # a second request will succeeds.
785
 
        self.assertEqual(2, self.get_readonly_server().GET_request_nb)
786
 
 
787
 
 
788
 
class TestLimitedRangeRequestServer_urllib(TestLimitedRangeRequestServer,
789
 
                                          TestCaseWithWebserver):
790
 
    """Tests limited range requests server for urllib implementation"""
791
 
 
792
 
    _transport = HttpTransport_urllib
793
 
 
794
 
 
795
 
class TestLimitedRangeRequestServer_pycurl(TestWithTransport_pycurl,
796
 
                                          TestLimitedRangeRequestServer,
797
 
                                          TestCaseWithWebserver):
798
 
    """Tests limited range requests server for pycurl implementation"""
799
 
 
800
 
 
801
 
 
802
 
class TestHttpProxyWhiteBox(tests.TestCase):
803
 
    """Whitebox test proxy http authorization.
804
 
 
805
 
    Only the urllib implementation is tested here.
806
 
    """
807
 
 
808
 
    def setUp(self):
809
 
        tests.TestCase.setUp(self)
810
 
        self._old_env = {}
811
 
 
812
 
    def tearDown(self):
813
 
        self._restore_env()
814
 
 
815
 
    def _install_env(self, env):
816
 
        for name, value in env.iteritems():
817
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
818
 
 
819
 
    def _restore_env(self):
820
 
        for name, value in self._old_env.iteritems():
821
 
            osutils.set_or_unset_env(name, value)
822
 
 
823
 
    def _proxied_request(self):
824
 
        handler = ProxyHandler()
825
 
        request = Request('GET','http://baz/buzzle')
826
 
        handler.set_proxy(request, 'http')
827
 
        return request
828
 
 
829
 
    def test_empty_user(self):
830
 
        self._install_env({'http_proxy': 'http://bar.com'})
831
 
        request = self._proxied_request()
832
 
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
833
 
 
834
 
    def test_invalid_proxy(self):
835
 
        """A proxy env variable without scheme"""
836
 
        self._install_env({'http_proxy': 'host:1234'})
837
 
        self.assertRaises(errors.InvalidURL, self._proxied_request)
838
 
 
839
 
 
840
 
class TestProxyHttpServer(object):
841
 
    """Tests proxy server.
842
 
 
843
 
    This MUST be used by daughter classes that also inherit from
844
 
    TestCaseWithTwoWebservers.
845
 
 
846
 
    We can't inherit directly from TestCaseWithTwoWebservers or
847
 
    the test framework will try to create an instance which
848
 
    cannot run, its implementation being incomplete.
849
 
 
850
 
    Be aware that we do not setup a real proxy here. Instead, we
851
 
    check that the *connection* goes through the proxy by serving
852
 
    different content (the faked proxy server append '-proxied'
853
 
    to the file names).
854
 
    """
855
 
 
856
 
    # FIXME: We don't have an https server available, so we don't
857
 
    # test https connections.
858
 
 
859
 
    def setUp(self):
860
 
        TestCaseWithTwoWebservers.setUp(self)
861
 
        self.build_tree_contents([('foo', 'contents of foo\n'),
862
 
                                  ('foo-proxied', 'proxied contents of foo\n')])
863
 
        # Let's setup some attributes for tests
864
 
        self.server = self.get_readonly_server()
865
 
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
866
 
        self.no_proxy_host = self.proxy_address
867
 
        # The secondary server is the proxy
868
 
        self.proxy = self.get_secondary_server()
869
 
        self.proxy_url = self.proxy.get_url()
870
 
        self._old_env = {}
871
 
 
872
 
    def create_transport_secondary_server(self):
873
 
        """Creates an http server that will serve files with
874
 
        '-proxied' appended to their names.
875
 
        """
876
 
        return ProxyServer()
877
 
 
878
 
    def _install_env(self, env):
879
 
        for name, value in env.iteritems():
880
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
881
 
 
882
 
    def _restore_env(self):
883
 
        for name, value in self._old_env.iteritems():
884
 
            osutils.set_or_unset_env(name, value)
885
 
 
886
 
    def proxied_in_env(self, env):
887
 
        self._install_env(env)
888
 
        url = self.server.get_url()
889
 
        t = self._transport(url)
890
 
        try:
891
 
            self.assertEqual(t.get('foo').read(), 'proxied contents of foo\n')
892
 
        finally:
893
 
            self._restore_env()
894
 
 
895
 
    def not_proxied_in_env(self, env):
896
 
        self._install_env(env)
897
 
        url = self.server.get_url()
898
 
        t = self._transport(url)
899
 
        try:
900
 
            self.assertEqual(t.get('foo').read(), 'contents of foo\n')
901
 
        finally:
902
 
            self._restore_env()
903
 
 
904
 
    def test_http_proxy(self):
905
 
        self.proxied_in_env({'http_proxy': self.proxy_url})
906
 
 
907
 
    def test_HTTP_PROXY(self):
908
 
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
909
 
 
910
 
    def test_all_proxy(self):
911
 
        self.proxied_in_env({'all_proxy': self.proxy_url})
912
 
 
913
 
    def test_ALL_PROXY(self):
914
 
        self.proxied_in_env({'ALL_PROXY': self.proxy_url})
915
 
 
916
 
    def test_http_proxy_with_no_proxy(self):
917
 
        self.not_proxied_in_env({'http_proxy': self.proxy_url,
918
 
                                 'no_proxy': self.no_proxy_host})
919
 
 
920
 
    def test_HTTP_PROXY_with_NO_PROXY(self):
921
 
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
922
 
                                 'NO_PROXY': self.no_proxy_host})
923
 
 
924
 
    def test_all_proxy_with_no_proxy(self):
925
 
        self.not_proxied_in_env({'all_proxy': self.proxy_url,
926
 
                                 'no_proxy': self.no_proxy_host})
927
 
 
928
 
    def test_ALL_PROXY_with_NO_PROXY(self):
929
 
        self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
930
 
                                 'NO_PROXY': self.no_proxy_host})
931
 
 
932
 
    def test_http_proxy_without_scheme(self):
933
 
        self.assertRaises(errors.InvalidURL,
934
 
                          self.proxied_in_env,
935
 
                          {'http_proxy': self.proxy_address})
936
 
 
937
 
 
938
 
class TestProxyHttpServer_urllib(TestProxyHttpServer,
939
 
                                 TestCaseWithTwoWebservers):
940
 
    """Tests proxy server for urllib implementation"""
941
 
 
942
 
    _transport = HttpTransport_urllib
943
 
 
944
 
 
945
 
class TestProxyHttpServer_pycurl(TestWithTransport_pycurl,
946
 
                                 TestProxyHttpServer,
947
 
                                 TestCaseWithTwoWebservers):
948
 
    """Tests proxy server for pycurl implementation"""
949
 
 
950
 
    def setUp(self):
951
 
        TestProxyHttpServer.setUp(self)
952
 
        # Oh my ! pycurl does not check for the port as part of
953
 
        # no_proxy :-( So we just test the host part
954
 
        self.no_proxy_host = 'localhost'
955
 
 
956
 
    def test_HTTP_PROXY(self):
957
 
        # pycurl does not check HTTP_PROXY for security reasons
958
 
        # (for use in a CGI context that we do not care
959
 
        # about. Should we ?)
960
 
        raise tests.TestNotApplicable(
961
 
            'pycurl does not check HTTP_PROXY for security reasons')
962
 
 
963
 
    def test_HTTP_PROXY_with_NO_PROXY(self):
964
 
        raise tests.TestNotApplicable(
965
 
            'pycurl does not check HTTP_PROXY for security reasons')
966
 
 
967
 
    def test_http_proxy_without_scheme(self):
968
 
        # pycurl *ignores* invalid proxy env variables. If that
969
 
        # ever change in the future, this test will fail
970
 
        # indicating that pycurl do not ignore anymore such
971
 
        # variables.
972
 
        self.not_proxied_in_env({'http_proxy': self.proxy_address})
973
 
 
974
 
 
975
 
class TestRanges(object):
976
 
    """Test the Range header in GET methods..
977
 
 
978
 
    This MUST be used by daughter classes that also inherit from
979
 
    TestCaseWithWebserver.
980
 
 
981
 
    We can't inherit directly from TestCaseWithWebserver or the
982
 
    test framework will try to create an instance which cannot
983
 
    run, its implementation being incomplete.
984
 
    """
985
 
 
986
 
    def setUp(self):
987
 
        TestCaseWithWebserver.setUp(self)
988
 
        self.build_tree_contents([('a', '0123456789')],)
989
 
        server = self.get_readonly_server()
990
 
        self.transport = self._transport(server.get_url())
991
 
 
992
 
    def _file_contents(self, relpath, ranges):
993
 
        offsets = [ (start, end - start + 1) for start, end in ranges]
994
 
        coalesce = self.transport._coalesce_offsets
995
 
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
996
 
        code, data = self.transport._get(relpath, coalesced)
997
 
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
998
 
        for start, end in ranges:
999
 
            data.seek(start)
1000
 
            yield data.read(end - start + 1)
1001
 
 
1002
 
    def _file_tail(self, relpath, tail_amount):
1003
 
        code, data = self.transport._get(relpath, [], tail_amount)
1004
 
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1005
 
        data.seek(-tail_amount, 2)
1006
 
        return data.read(tail_amount)
1007
 
 
1008
 
    def test_range_header(self):
1009
 
        # Valid ranges
1010
 
        map(self.assertEqual,['0', '234'],
1011
 
            list(self._file_contents('a', [(0,0), (2,4)])),)
1012
 
        # Tail
1013
 
        self.assertEqual('789', self._file_tail('a', 3))
1014
 
        # Syntactically invalid range
1015
 
        self.assertListRaises(errors.InvalidHttpRange,
1016
 
                          self._file_contents, 'a', [(4, 3)])
1017
 
        # Semantically invalid range
1018
 
        self.assertListRaises(errors.InvalidHttpRange,
1019
 
                          self._file_contents, 'a', [(42, 128)])
1020
 
 
1021
 
 
1022
 
class TestRanges_urllib(TestRanges, TestCaseWithWebserver):
1023
 
    """Test the Range header in GET methods for urllib implementation"""
1024
 
 
1025
 
    _transport = HttpTransport_urllib
1026
 
 
1027
 
 
1028
 
class TestRanges_pycurl(TestWithTransport_pycurl,
1029
 
                        TestRanges,
1030
 
                        TestCaseWithWebserver):
1031
 
    """Test the Range header in GET methods for pycurl implementation"""
1032
 
 
1033
 
 
1034
 
class TestHTTPRedirections(object):
1035
 
    """Test redirection between http servers.
1036
 
 
1037
 
    This MUST be used by daughter classes that also inherit from
1038
 
    TestCaseWithRedirectedWebserver.
1039
 
 
1040
 
    We can't inherit directly from TestCaseWithTwoWebservers or the
1041
 
    test framework will try to create an instance which cannot
1042
 
    run, its implementation being incomplete. 
1043
 
    """
1044
 
 
1045
 
    def create_transport_secondary_server(self):
1046
 
        """Create the secondary server redirecting to the primary server"""
1047
 
        new = self.get_readonly_server()
1048
 
 
1049
 
        redirecting = HTTPServerRedirecting()
1050
 
        redirecting.redirect_to(new.host, new.port)
1051
 
        return redirecting
1052
 
 
1053
 
    def setUp(self):
1054
 
        super(TestHTTPRedirections, self).setUp()
1055
 
        self.build_tree_contents([('a', '0123456789'),
1056
 
                                  ('bundle',
1057
 
                                  '# Bazaar revision bundle v0.9\n#\n')
1058
 
                                  ],)
1059
 
 
1060
 
        self.old_transport = self._transport(self.old_server.get_url())
1061
 
 
1062
 
    def test_redirected(self):
1063
 
        self.assertRaises(errors.RedirectRequested, self.old_transport.get, 'a')
1064
 
        t = self._transport(self.new_server.get_url())
1065
 
        self.assertEqual('0123456789', t.get('a').read())
1066
 
 
1067
 
    def test_read_redirected_bundle_from_url(self):
1068
 
        from bzrlib.bundle import read_bundle_from_url
1069
 
        url = self.old_transport.abspath('bundle')
1070
 
        bundle = read_bundle_from_url(url)
1071
 
        # If read_bundle_from_url was successful we get an empty bundle
1072
 
        self.assertEqual([], bundle.revisions)
1073
 
 
1074
 
 
1075
 
class TestHTTPRedirections_urllib(TestHTTPRedirections,
1076
 
                                  TestCaseWithRedirectedWebserver):
1077
 
    """Tests redirections for urllib implementation"""
1078
 
 
1079
 
    _transport = HttpTransport_urllib
1080
 
 
1081
 
 
1082
 
 
1083
 
class TestHTTPRedirections_pycurl(TestWithTransport_pycurl,
1084
 
                                  TestHTTPRedirections,
1085
 
                                  TestCaseWithRedirectedWebserver):
1086
 
    """Tests redirections for pycurl implementation"""
1087
 
 
1088
 
 
1089
 
class RedirectedRequest(Request):
1090
 
    """Request following redirections"""
1091
 
 
1092
 
    init_orig = Request.__init__
1093
 
 
1094
 
    def __init__(self, method, url, *args, **kwargs):
1095
 
        RedirectedRequest.init_orig(self, method, url, args, kwargs)
1096
 
        self.follow_redirections = True
1097
 
 
1098
 
 
1099
 
class TestHTTPSilentRedirections_urllib(TestCaseWithRedirectedWebserver):
1100
 
    """Test redirections provided by urllib.
1101
 
 
1102
 
    http implementations do not redirect silently anymore (they
1103
 
    do not redirect at all in fact). The mechanism is still in
1104
 
    place at the _urllib2_wrappers.Request level and these tests
1105
 
    exercise it.
1106
 
 
1107
 
    For the pycurl implementation
1108
 
    the redirection have been deleted as we may deprecate pycurl
1109
 
    and I have no place to keep a working implementation.
1110
 
    -- vila 20070212
1111
 
    """
1112
 
 
1113
 
    _transport = HttpTransport_urllib
1114
 
 
1115
 
    def setUp(self):
1116
 
        super(TestHTTPSilentRedirections_urllib, self).setUp()
1117
 
        self.setup_redirected_request()
1118
 
        self.addCleanup(self.cleanup_redirected_request)
1119
 
        self.build_tree_contents([('a','a'),
1120
 
                                  ('1/',),
1121
 
                                  ('1/a', 'redirected once'),
1122
 
                                  ('2/',),
1123
 
                                  ('2/a', 'redirected twice'),
1124
 
                                  ('3/',),
1125
 
                                  ('3/a', 'redirected thrice'),
1126
 
                                  ('4/',),
1127
 
                                  ('4/a', 'redirected 4 times'),
1128
 
                                  ('5/',),
1129
 
                                  ('5/a', 'redirected 5 times'),
1130
 
                                  ],)
1131
 
 
1132
 
        self.old_transport = self._transport(self.old_server.get_url())
1133
 
 
1134
 
    def setup_redirected_request(self):
1135
 
        self.original_class = _urllib2_wrappers.Request
1136
 
        _urllib2_wrappers.Request = RedirectedRequest
1137
 
 
1138
 
    def cleanup_redirected_request(self):
1139
 
        _urllib2_wrappers.Request = self.original_class
1140
 
 
1141
 
    def create_transport_secondary_server(self):
1142
 
        """Create the secondary server, redirections are defined in the tests"""
1143
 
        return HTTPServerRedirecting()
1144
 
 
1145
 
    def test_one_redirection(self):
1146
 
        t = self.old_transport
1147
 
 
1148
 
        req = RedirectedRequest('GET', t.abspath('a'))
1149
 
        req.follow_redirections = True
1150
 
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1151
 
                                       self.new_server.port)
1152
 
        self.old_server.redirections = \
1153
 
            [('(.*)', r'%s/1\1' % (new_prefix), 301),]
1154
 
        self.assertEquals('redirected once',t._perform(req).read())
1155
 
 
1156
 
    def test_five_redirections(self):
1157
 
        t = self.old_transport
1158
 
 
1159
 
        req = RedirectedRequest('GET', t.abspath('a'))
1160
 
        req.follow_redirections = True
1161
 
        old_prefix = 'http://%s:%s' % (self.old_server.host,
1162
 
                                       self.old_server.port)
1163
 
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1164
 
                                       self.new_server.port)
1165
 
        self.old_server.redirections = \
1166
 
            [('/1(.*)', r'%s/2\1' % (old_prefix), 302),
1167
 
             ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
1168
 
             ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
1169
 
             ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1170
 
             ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1171
 
             ]
1172
 
        self.assertEquals('redirected 5 times',t._perform(req).read())
1173
 
 
1174
 
 
1175
 
class TestDoCatchRedirections(TestCaseWithRedirectedWebserver):
1176
 
    """Test transport.do_catching_redirections.
1177
 
 
1178
 
    We arbitrarily choose to use urllib transports
1179
 
    """
1180
 
 
1181
 
    _transport = HttpTransport_urllib
1182
 
 
1183
 
    def setUp(self):
1184
 
        super(TestDoCatchRedirections, self).setUp()
1185
 
        self.build_tree_contents([('a', '0123456789'),],)
1186
 
 
1187
 
        self.old_transport = self._transport(self.old_server.get_url())
1188
 
 
1189
 
    def get_a(self, transport):
1190
 
        return transport.get('a')
1191
 
 
1192
 
    def test_no_redirection(self):
1193
 
        t = self._transport(self.new_server.get_url())
1194
 
 
1195
 
        # We use None for redirected so that we fail if redirected
1196
 
        self.assertEquals('0123456789',
1197
 
                          do_catching_redirections(self.get_a, t, None).read())
1198
 
 
1199
 
    def test_one_redirection(self):
1200
 
        self.redirections = 0
1201
 
 
1202
 
        def redirected(transport, exception, redirection_notice):
1203
 
            self.redirections += 1
1204
 
            dir, file = urlutils.split(exception.target)
1205
 
            return self._transport(dir)
1206
 
 
1207
 
        self.assertEquals('0123456789',
1208
 
                          do_catching_redirections(self.get_a,
1209
 
                                                   self.old_transport,
1210
 
                                                   redirected
1211
 
                                                   ).read())
1212
 
        self.assertEquals(1, self.redirections)
1213
 
 
1214
 
    def test_redirection_loop(self):
1215
 
 
1216
 
        def redirected(transport, exception, redirection_notice):
1217
 
            # By using the redirected url as a base dir for the
1218
 
            # *old* transport, we create a loop: a => a/a =>
1219
 
            # a/a/a
1220
 
            return self.old_transport.clone(exception.target)
1221
 
 
1222
 
        self.assertRaises(errors.TooManyRedirections, do_catching_redirections,
1223
 
                          self.get_a, self.old_transport, redirected)
1224
 
 
1225
 
 
1226
 
class TestAuth(object):
1227
 
    """Test some authentication scheme specified by daughter class.
1228
 
 
1229
 
    This MUST be used by daughter classes that also inherit from
1230
 
    either TestCaseWithWebserver or TestCaseWithTwoWebservers.
1231
 
    """
1232
 
 
1233
 
    _password_prompt_prefix = ''
1234
 
 
1235
 
    def setUp(self):
1236
 
        """Set up the test environment
1237
 
 
1238
 
        Daughter classes should set up their own environment
1239
 
        (including self.server) and explicitely call this
1240
 
        method. This is needed because we want to reuse the same
1241
 
        tests for proxy and no-proxy accesses which have
1242
 
        different ways of setting self.server.
1243
 
        """
1244
 
        self.build_tree_contents([('a', 'contents of a\n'),
1245
 
                                  ('b', 'contents of b\n'),])
1246
 
 
1247
 
    def get_user_url(self, user=None, password=None):
1248
 
        """Build an url embedding user and password"""
1249
 
        url = '%s://' % self.server._url_protocol
1250
 
        if user is not None:
1251
 
            url += user
1252
 
            if password is not None:
1253
 
                url += ':' + password
1254
 
            url += '@'
1255
 
        url += '%s:%s/' % (self.server.host, self.server.port)
1256
 
        return url
1257
 
 
1258
 
    def test_no_user(self):
1259
 
        self.server.add_user('joe', 'foo')
1260
 
        t = self.get_user_transport()
1261
 
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1262
 
        # Only one 'Authentication Required' error should occur
1263
 
        self.assertEqual(1, self.server.auth_required_errors)
1264
 
 
1265
 
    def test_empty_pass(self):
1266
 
        self.server.add_user('joe', '')
1267
 
        t = self.get_user_transport('joe', '')
1268
 
        self.assertEqual('contents of a\n', t.get('a').read())
1269
 
        # Only one 'Authentication Required' error should occur
1270
 
        self.assertEqual(1, self.server.auth_required_errors)
1271
 
 
1272
 
    def test_user_pass(self):
1273
 
        self.server.add_user('joe', 'foo')
1274
 
        t = self.get_user_transport('joe', 'foo')
1275
 
        self.assertEqual('contents of a\n', t.get('a').read())
1276
 
        # Only one 'Authentication Required' error should occur
1277
 
        self.assertEqual(1, self.server.auth_required_errors)
1278
 
 
1279
 
    def test_unknown_user(self):
1280
 
        self.server.add_user('joe', 'foo')
1281
 
        t = self.get_user_transport('bill', 'foo')
1282
 
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1283
 
        # Two 'Authentication Required' errors should occur (the
1284
 
        # initial 'who are you' and 'I don't know you, who are
1285
 
        # you').
1286
 
        self.assertEqual(2, self.server.auth_required_errors)
1287
 
 
1288
 
    def test_wrong_pass(self):
1289
 
        self.server.add_user('joe', 'foo')
1290
 
        t = self.get_user_transport('joe', 'bar')
1291
 
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1292
 
        # Two 'Authentication Required' errors should occur (the
1293
 
        # initial 'who are you' and 'this is not you, who are you')
1294
 
        self.assertEqual(2, self.server.auth_required_errors)
1295
 
 
1296
 
    def test_prompt_for_password(self):
1297
 
        self.server.add_user('joe', 'foo')
1298
 
        t = self.get_user_transport('joe', None)
1299
 
        stdout = tests.StringIOWrapper()
1300
 
        ui.ui_factory = tests.TestUIFactory(stdin='foo\n', stdout=stdout)
1301
 
        self.assertEqual('contents of a\n',t.get('a').read())
1302
 
        # stdin should be empty
1303
 
        self.assertEqual('', ui.ui_factory.stdin.readline())
1304
 
        self._check_password_prompt(t._unqualified_scheme, 'joe',
1305
 
                                    stdout.getvalue())
1306
 
        # And we shouldn't prompt again for a different request
1307
 
        # against the same transport.
1308
 
        self.assertEqual('contents of b\n',t.get('b').read())
1309
 
        t2 = t.clone()
1310
 
        # And neither against a clone
1311
 
        self.assertEqual('contents of b\n',t2.get('b').read())
1312
 
        # Only one 'Authentication Required' error should occur
1313
 
        self.assertEqual(1, self.server.auth_required_errors)
1314
 
 
1315
 
    def _check_password_prompt(self, scheme, user, actual_prompt):
1316
 
        expected_prompt = (self._password_prompt_prefix
1317
 
                           + ("%s %s@%s:%d, Realm: '%s' password: "
1318
 
                              % (scheme.upper(),
1319
 
                                 user, self.server.host, self.server.port,
1320
 
                                 self.server.auth_realm)))
1321
 
        self.assertEquals(expected_prompt, actual_prompt)
1322
 
 
1323
 
    def test_no_prompt_for_password_when_using_auth_config(self):
1324
 
        user =' joe'
1325
 
        password = 'foo'
1326
 
        stdin_content = 'bar\n'  # Not the right password
1327
 
        self.server.add_user(user, password)
1328
 
        t = self.get_user_transport(user, None)
1329
 
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1330
 
                                            stdout=tests.StringIOWrapper())
1331
 
        # Create a minimal config file with the right password
1332
 
        conf = config.AuthenticationConfig()
1333
 
        conf._get_config().update(
1334
 
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1335
 
                          'user': user, 'password': password}})
1336
 
        conf._save()
1337
 
        # Issue a request to the server to connect
1338
 
        self.assertEqual('contents of a\n',t.get('a').read())
1339
 
        # stdin should have  been left untouched
1340
 
        self.assertEqual(stdin_content, ui.ui_factory.stdin.readline())
1341
 
        # Only one 'Authentication Required' error should occur
1342
 
        self.assertEqual(1, self.server.auth_required_errors)
1343
 
 
1344
 
 
1345
 
 
1346
 
class TestHTTPAuth(TestAuth):
1347
 
    """Test HTTP authentication schemes.
1348
 
 
1349
 
    Daughter classes MUST inherit from TestCaseWithWebserver too.
1350
 
    """
1351
 
 
1352
 
    _auth_header = 'Authorization'
1353
 
 
1354
 
    def setUp(self):
1355
 
        TestCaseWithWebserver.setUp(self)
1356
 
        self.server = self.get_readonly_server()
1357
 
        TestAuth.setUp(self)
1358
 
 
1359
 
    def get_user_transport(self, user=None, password=None):
1360
 
        return self._transport(self.get_user_url(user, password))
1361
 
 
1362
 
 
1363
 
class TestProxyAuth(TestAuth):
1364
 
    """Test proxy authentication schemes.
1365
 
 
1366
 
    Daughter classes MUST also inherit from TestCaseWithWebserver.
1367
 
    """
1368
 
    _auth_header = 'Proxy-authorization'
1369
 
    _password_prompt_prefix = 'Proxy '
1370
 
 
1371
 
 
1372
 
    def setUp(self):
1373
 
        TestCaseWithWebserver.setUp(self)
1374
 
        self.server = self.get_readonly_server()
1375
 
        self._old_env = {}
1376
 
        self.addCleanup(self._restore_env)
1377
 
        TestAuth.setUp(self)
1378
 
        # Override the contents to avoid false positives
1379
 
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
1380
 
                                  ('b', 'not proxied contents of b\n'),
1381
 
                                  ('a-proxied', 'contents of a\n'),
1382
 
                                  ('b-proxied', 'contents of b\n'),
1383
 
                                  ])
1384
 
 
1385
 
    def get_user_transport(self, user=None, password=None):
1386
 
        self._install_env({'all_proxy': self.get_user_url(user, password)})
1387
 
        return self._transport(self.server.get_url())
1388
 
 
1389
 
    def _install_env(self, env):
1390
 
        for name, value in env.iteritems():
1391
 
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1392
 
 
1393
 
    def _restore_env(self):
1394
 
        for name, value in self._old_env.iteritems():
1395
 
            osutils.set_or_unset_env(name, value)
1396
 
 
1397
 
 
1398
 
class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
1399
 
    """Test http basic authentication scheme"""
1400
 
 
1401
 
    _transport = HttpTransport_urllib
1402
 
 
1403
 
    def create_transport_readonly_server(self):
1404
 
        return HTTPBasicAuthServer()
1405
 
 
1406
 
 
1407
 
class TestHTTPProxyBasicAuth(TestProxyAuth, TestCaseWithWebserver):
1408
 
    """Test proxy basic authentication scheme"""
1409
 
 
1410
 
    _transport = HttpTransport_urllib
1411
 
 
1412
 
    def create_transport_readonly_server(self):
1413
 
        return ProxyBasicAuthServer()
1414
 
 
1415
 
 
1416
 
class TestDigestAuth(object):
1417
 
    """Digest Authentication specific tests"""
1418
 
 
1419
 
    def test_changing_nonce(self):
1420
 
        self.server.add_user('joe', 'foo')
1421
 
        t = self.get_user_transport('joe', 'foo')
1422
 
        self.assertEqual('contents of a\n', t.get('a').read())
1423
 
        self.assertEqual('contents of b\n', t.get('b').read())
1424
 
        # Only one 'Authentication Required' error should have
1425
 
        # occured so far
1426
 
        self.assertEqual(1, self.server.auth_required_errors)
1427
 
        # The server invalidates the current nonce
1428
 
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1429
 
        self.assertEqual('contents of a\n', t.get('a').read())
1430
 
        # Two 'Authentication Required' errors should occur (the
1431
 
        # initial 'who are you' and a second 'who are you' with the new nonce)
1432
 
        self.assertEqual(2, self.server.auth_required_errors)
1433
 
 
1434
 
 
1435
 
class TestHTTPDigestAuth(TestHTTPAuth, TestDigestAuth, TestCaseWithWebserver):
1436
 
    """Test http digest authentication scheme"""
1437
 
 
1438
 
    _transport = HttpTransport_urllib
1439
 
 
1440
 
    def create_transport_readonly_server(self):
1441
 
        return HTTPDigestAuthServer()
1442
 
 
1443
 
 
1444
 
class TestHTTPProxyDigestAuth(TestProxyAuth, TestDigestAuth,
1445
 
                              TestCaseWithWebserver):
1446
 
    """Test proxy digest authentication scheme"""
1447
 
 
1448
 
    _transport = HttpTransport_urllib
1449
 
 
1450
 
    def create_transport_readonly_server(self):
1451
 
        return ProxyDigestAuthServer()
1452