/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

Merge bzr.dev

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