~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Robert Collins
  • Date: 2005-09-28 05:25:54 UTC
  • mfrom: (1185.1.42)
  • mto: (1092.2.18)
  • mto: This revision was merged to the branch mainline in revision 1397.
  • Revision ID: robertc@robertcollins.net-20050928052554-beb985505f77ea6a
update symlink branch to integration

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