/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
1
# Copyright (C) 2005, 2006 Canonical Ltd
1540.3.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
2
#
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
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.
1540.3.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
7
#
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
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.
1540.3.24 by Martin Pool
Add new protocol 'http+pycurl' that always uses PyCurl.
12
#
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
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
1185.16.68 by Martin Pool
- http url fixes suggested by Robey Pointer, and tests
16
3111.1.30 by Vincent Ladeuil
Update NEWS. Some cosmetic changes.
17
"""Tests for HTTP implementations.
3111.1.10 by Vincent Ladeuil
Finish http parameterization, 24 auth tests failing for pycurl (not
18
3111.1.30 by Vincent Ladeuil
Update NEWS. Some cosmetic changes.
19
This module defines a load_tests() method that parametrize tests classes for
20
transport implementation, http protocol versions and authentication schemes.
3111.1.10 by Vincent Ladeuil
Finish http parameterization, 24 auth tests failing for pycurl (not
21
"""
1540.3.3 by Martin Pool
Review updates of pycurl transport
22
1540.3.22 by Martin Pool
[patch] Add TestCase.assertIsInstance
23
# TODO: Should be renamed to bzrlib.transport.http.tests?
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
24
# TODO: What about renaming to bzrlib.tests.transport.http ?
1540.3.22 by Martin Pool
[patch] Add TestCase.assertIsInstance
25
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
26
from cStringIO import StringIO
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
27
import httplib
2167.3.5 by v.ladeuil+lp at free
Tests for proxies, covering #74759.
28
import os
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
29
import select
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
30
import SimpleHTTPServer
2000.2.2 by John Arbash Meinel
Update the urllib.has test.
31
import socket
2420.1.20 by Vincent Ladeuil
Fix test failure on pqm.
32
import sys
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
33
import threading
2000.2.2 by John Arbash Meinel
Update the urllib.has test.
34
1553.1.2 by James Henstridge
Add a test to make sure the user-agent header is being sent correctly.
35
import bzrlib
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
36
from bzrlib import (
2900.2.6 by Vincent Ladeuil
Make http aware of authentication config.
37
    config,
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
38
    errors,
39
    osutils,
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
40
    tests,
3111.1.10 by Vincent Ladeuil
Finish http parameterization, 24 auth tests failing for pycurl (not
41
    transport,
2363.4.12 by Vincent Ladeuil
Take jam's review comments into account. Fix typos, give better
42
    ui,
2164.2.22 by Vincent Ladeuil
Take Aaron's review comments into account.
43
    urlutils,
44
    )
3102.1.1 by Vincent Ladeuil
Rename bzrlib/test/HTTPTestUtils.py to bzrlib/tests/http_utils.py and fix
45
from bzrlib.tests import (
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
46
    http_server,
3111.1.7 by Vincent Ladeuil
Further refactoring.
47
    http_utils,
3102.1.1 by Vincent Ladeuil
Rename bzrlib/test/HTTPTestUtils.py to bzrlib/tests/http_utils.py and fix
48
    )
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
49
from bzrlib.transport import (
50
    http,
51
    remote,
52
    )
2004.3.3 by vila
Better (but still incomplete) design for bogus servers.
53
from bzrlib.transport.http import (
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
54
    _urllib,
2164.2.29 by Vincent Ladeuil
Test the http redirection at the request level even if it's not
55
    _urllib2_wrappers,
2004.3.3 by vila
Better (but still incomplete) design for bogus servers.
56
    )
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
57
58
59
try:
60
    from bzrlib.transport.http._pycurl import PyCurlTransport
61
    pycurl_present = True
62
except errors.DependencyNotPresent:
63
    pycurl_present = False
64
65
66
class TransportAdapter(tests.TestScenarioApplier):
67
    """Generate the same test for each transport implementation."""
68
69
    def __init__(self):
70
        transport_scenarios = [
71
            ('urllib', dict(_transport=_urllib.HttpTransport_urllib,
72
                            _server=http_server.HttpServer_urllib,
73
                            _qualified_prefix='http+urllib',)),
74
            ]
75
        if pycurl_present:
76
            transport_scenarios.append(
77
                ('pycurl', dict(_transport=PyCurlTransport,
78
                                _server=http_server.HttpServer_PyCurl,
79
                                _qualified_prefix='http+pycurl',)))
80
        self.scenarios = transport_scenarios
81
82
83
class TransportProtocolAdapter(TransportAdapter):
84
    """Generate the same test for each protocol implementation.
85
86
    In addition to the transport adaptatation that we inherit from.
87
    """
88
89
    def __init__(self):
90
        super(TransportProtocolAdapter, self).__init__()
91
        protocol_scenarios = [
92
            ('HTTP/1.0',  dict(_protocol_version='HTTP/1.0')),
93
            ('HTTP/1.1',  dict(_protocol_version='HTTP/1.1')),
94
            ]
95
        self.scenarios = tests.multiply_scenarios(self.scenarios,
96
                                                  protocol_scenarios)
97
98
99
class TransportProtocolAuthenticationAdapter(TransportProtocolAdapter):
100
    """Generate the same test for each authentication scheme implementation.
101
102
    In addition to the protocol adaptatation that we inherit from.
103
    """
104
105
    def __init__(self):
106
        super(TransportProtocolAuthenticationAdapter, self).__init__()
107
        auth_scheme_scenarios = [
108
            ('basic', dict(_auth_scheme='basic')),
109
            ('digest', dict(_auth_scheme='digest')),
110
            ]
111
112
        self.scenarios = tests.multiply_scenarios(self.scenarios,
113
                                                  auth_scheme_scenarios)
114
115
def load_tests(standard_tests, module, loader):
116
    """Multiply tests for http clients and protocol versions."""
117
    # one for each transport
118
    t_adapter = TransportAdapter()
119
    t_classes= (TestHttpTransportRegistration,
120
                TestHttpTransportUrls,
121
                )
122
    is_testing_for_transports = tests.condition_isinstance(t_classes)
123
124
    # multiplied by one for each protocol version
125
    tp_adapter = TransportProtocolAdapter()
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
126
    tp_classes= (SmartHTTPTunnellingTest,
127
                 TestDoCatchRedirections,
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
128
                 TestHTTPConnections,
129
                 TestHTTPRedirections,
130
                 TestHTTPSilentRedirections,
131
                 TestLimitedRangeRequestServer,
132
                 TestPost,
133
                 TestProxyHttpServer,
134
                 TestRanges,
135
                 TestSpecificRequestHandler,
136
                 )
137
    is_also_testing_for_protocols = tests.condition_isinstance(tp_classes)
138
139
    # multiplied by one for each authentication scheme
140
    tpa_adapter = TransportProtocolAuthenticationAdapter()
141
    tpa_classes = (TestAuth,
142
                   )
3111.1.30 by Vincent Ladeuil
Update NEWS. Some cosmetic changes.
143
    is_also_testing_for_authentication = tests.condition_isinstance(
144
        tpa_classes)
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
145
146
    result = loader.suiteClass()
3111.1.24 by Vincent Ladeuil
Cleanups.
147
    for test_class in tests.iter_suite_tests(standard_tests):
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
148
        # Each test class is either standalone or testing for some combination
149
        # of transport, protocol version, authentication scheme. Use the right
150
        # adpater (or none) depending on the class.
3111.1.24 by Vincent Ladeuil
Cleanups.
151
        if is_testing_for_transports(test_class):
152
            result.addTests(t_adapter.adapt(test_class))
153
        elif is_also_testing_for_protocols(test_class):
154
            result.addTests(tp_adapter.adapt(test_class))
155
        elif is_also_testing_for_authentication(test_class):
156
            result.addTests(tpa_adapter.adapt(test_class))
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
157
        else:
3111.1.24 by Vincent Ladeuil
Cleanups.
158
            result.addTest(test_class)
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
159
    return result
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
160
1786.1.8 by John Arbash Meinel
[merge] Johan Rydberg test updates
161
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
162
class FakeManager(object):
1786.1.8 by John Arbash Meinel
[merge] Johan Rydberg test updates
163
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
164
    def __init__(self):
165
        self.credentials = []
2004.3.1 by vila
Test ConnectionError exceptions.
166
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
167
    def add_password(self, realm, host, username, password):
168
        self.credentials.append([realm, host, username, password])
169
1553.1.2 by James Henstridge
Add a test to make sure the user-agent header is being sent correctly.
170
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
171
class RecordingServer(object):
172
    """A fake HTTP server.
173
    
174
    It records the bytes sent to it, and replies with a 200.
175
    """
176
177
    def __init__(self, expect_body_tail=None):
178
        """Constructor.
179
180
        :type expect_body_tail: str
181
        :param expect_body_tail: a reply won't be sent until this string is
182
            received.
183
        """
184
        self._expect_body_tail = expect_body_tail
185
        self.host = None
186
        self.port = None
187
        self.received_bytes = ''
188
189
    def setUp(self):
190
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
191
        self._sock.bind(('127.0.0.1', 0))
192
        self.host, self.port = self._sock.getsockname()
193
        self._ready = threading.Event()
194
        self._thread = threading.Thread(target=self._accept_read_and_reply)
195
        self._thread.setDaemon(True)
196
        self._thread.start()
197
        self._ready.wait(5)
198
199
    def _accept_read_and_reply(self):
200
        self._sock.listen(1)
201
        self._ready.set()
202
        self._sock.settimeout(5)
203
        try:
204
            conn, address = self._sock.accept()
205
            # On win32, the accepted connection will be non-blocking to start
206
            # with because we're using settimeout.
207
            conn.setblocking(True)
208
            while not self.received_bytes.endswith(self._expect_body_tail):
209
                self.received_bytes += conn.recv(4096)
210
            conn.sendall('HTTP/1.1 200 OK\r\n')
211
        except socket.timeout:
212
            # Make sure the client isn't stuck waiting for us to e.g. accept.
213
            self._sock.close()
214
        except socket.error:
215
            # The client may have already closed the socket.
216
            pass
217
218
    def tearDown(self):
219
        try:
220
            self._sock.close()
221
        except socket.error:
222
            # We might have already closed it.  We don't care.
223
            pass
224
        self.host = None
225
        self.port = None
226
227
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
228
class TestHTTPServer(tests.TestCase):
229
    """Test the HTTP servers implementations."""
230
231
    def test_invalid_protocol(self):
232
        class BogusRequestHandler(http_server.TestingHTTPRequestHandler):
233
234
            protocol_version = 'HTTP/0.1'
235
236
        server = http_server.HttpServer(BogusRequestHandler)
237
        try:
238
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
239
        except:
240
            server.tearDown()
241
            self.fail('HTTP Server creation did not raise UnknownProtocol')
242
3111.1.17 by Vincent Ladeuil
Add tests for the protocol version parameter.
243
    def test_force_invalid_protocol(self):
244
        server = http_server.HttpServer(protocol_version='HTTP/0.1')
245
        try:
246
            self.assertRaises(httplib.UnknownProtocol,server.setUp)
247
        except:
248
            server.tearDown()
249
            self.fail('HTTP Server creation did not raise UnknownProtocol')
250
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
251
    def test_server_start_and_stop(self):
252
        server = http_server.HttpServer()
253
        server.setUp()
254
        self.assertTrue(server._http_running)
255
        server.tearDown()
256
        self.assertFalse(server._http_running)
257
258
    def test_create_http_server_one_zero(self):
259
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
260
261
            protocol_version = 'HTTP/1.0'
262
263
        server = http_server.HttpServer(RequestHandlerOneZero)
264
        server.setUp()
3111.1.17 by Vincent Ladeuil
Add tests for the protocol version parameter.
265
        self.addCleanup(server.tearDown)
266
        self.assertIsInstance(server._httpd, http_server.TestingHTTPServer)
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
267
268
    def test_create_http_server_one_one(self):
269
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
270
271
            protocol_version = 'HTTP/1.1'
272
273
        server = http_server.HttpServer(RequestHandlerOneOne)
274
        server.setUp()
3111.1.17 by Vincent Ladeuil
Add tests for the protocol version parameter.
275
        self.addCleanup(server.tearDown)
276
        self.assertIsInstance(server._httpd,
277
                              http_server.TestingThreadingHTTPServer)
278
279
    def test_create_http_server_force_one_one(self):
280
        class RequestHandlerOneZero(http_server.TestingHTTPRequestHandler):
281
282
            protocol_version = 'HTTP/1.0'
283
284
        server = http_server.HttpServer(RequestHandlerOneZero,
285
                                        protocol_version='HTTP/1.1')
286
        server.setUp()
287
        self.addCleanup(server.tearDown)
288
        self.assertIsInstance(server._httpd,
289
                              http_server.TestingThreadingHTTPServer)
290
291
    def test_create_http_server_force_one_zero(self):
292
        class RequestHandlerOneOne(http_server.TestingHTTPRequestHandler):
293
294
            protocol_version = 'HTTP/1.1'
295
296
        server = http_server.HttpServer(RequestHandlerOneOne,
297
                                        protocol_version='HTTP/1.0')
298
        server.setUp()
299
        self.addCleanup(server.tearDown)
300
        self.assertIsInstance(server._httpd,
301
                              http_server.TestingHTTPServer)
3111.1.4 by Vincent Ladeuil
Select the server depending on the request handler protocol. Add tests.
302
303
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
304
class TestWithTransport_pycurl(object):
305
    """Test case to inherit from if pycurl is present"""
306
307
    def _get_pycurl_maybe(self):
308
        try:
309
            from bzrlib.transport.http._pycurl import PyCurlTransport
310
            return PyCurlTransport
311
        except errors.DependencyNotPresent:
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
312
            raise tests.TestSkipped('pycurl not present')
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
313
314
    _transport = property(_get_pycurl_maybe)
315
316
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
317
class TestHttpUrls(tests.TestCase):
1786.1.8 by John Arbash Meinel
[merge] Johan Rydberg test updates
318
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
319
    # TODO: This should be moved to authorization tests once they
320
    # are written.
2004.1.40 by v.ladeuil+lp at free
Fix the race condition again and correct some small typos to be in
321
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
322
    def test_url_parsing(self):
323
        f = FakeManager()
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
324
        url = http.extract_auth('http://example.com', f)
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
325
        self.assertEquals('http://example.com', url)
326
        self.assertEquals(0, len(f.credentials))
3111.1.30 by Vincent Ladeuil
Update NEWS. Some cosmetic changes.
327
        url = http.extract_auth(
328
            'http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
1185.50.94 by John Arbash Meinel
Updated web page url to http://bazaar-vcs.org
329
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
1185.40.20 by Robey Pointer
allow user:pass@ info in http urls to be used for auth; this should be easily expandable later to use auth config files
330
        self.assertEquals(1, len(f.credentials))
2004.3.1 by vila
Test ConnectionError exceptions.
331
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
332
                          f.credentials[0])
333
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
334
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
335
class TestHttpTransportUrls(tests.TestCase):
336
    """Test the http urls."""
337
338
    def test_abs_url(self):
339
        """Construction of absolute http URLs"""
340
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
341
        eq = self.assertEqualDiff
342
        eq(t.abspath('.'), 'http://bazaar-vcs.org/bzr/bzr.dev')
343
        eq(t.abspath('foo/bar'), 'http://bazaar-vcs.org/bzr/bzr.dev/foo/bar')
344
        eq(t.abspath('.bzr'), 'http://bazaar-vcs.org/bzr/bzr.dev/.bzr')
345
        eq(t.abspath('.bzr/1//2/./3'),
346
           'http://bazaar-vcs.org/bzr/bzr.dev/.bzr/1/2/3')
347
348
    def test_invalid_http_urls(self):
349
        """Trap invalid construction of urls"""
350
        t = self._transport('http://bazaar-vcs.org/bzr/bzr.dev/')
351
        self.assertRaises(errors.InvalidURL,
352
                          self._transport,
353
                          'http://http://bazaar-vcs.org/bzr/bzr.dev/')
354
355
    def test_http_root_urls(self):
356
        """Construction of URLs from server root"""
357
        t = self._transport('http://bzr.ozlabs.org/')
358
        eq = self.assertEqualDiff
359
        eq(t.abspath('.bzr/tree-version'),
360
           'http://bzr.ozlabs.org/.bzr/tree-version')
361
362
    def test_http_impl_urls(self):
363
        """There are servers which ask for particular clients to connect"""
364
        server = self._server()
365
        try:
366
            server.setUp()
367
            url = server.get_url()
368
            self.assertTrue(url.startswith('%s://' % self._qualified_prefix))
369
        finally:
370
            server.tearDown()
371
372
3111.1.9 by Vincent Ladeuil
Most refactoring regarding parameterization for urllib/pycurl and custom
373
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
374
375
    # TODO: This should really be moved into another pycurl
376
    # specific test. When https tests will be implemented, take
377
    # this one into account.
378
    def test_pycurl_without_https_support(self):
379
        """Test that pycurl without SSL do not fail with a traceback.
380
381
        For the purpose of the test, we force pycurl to ignore
382
        https by supplying a fake version_info that do not
383
        support it.
384
        """
385
        try:
386
            import pycurl
387
        except ImportError:
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
388
            raise tests.TestSkipped('pycurl not present')
3111.1.14 by Vincent Ladeuil
Fix test leakage.
389
390
        version_info_orig = pycurl.version_info
391
        try:
392
            # Now that we have pycurl imported, we can fake its version_info
393
            # This was taken from a windows pycurl without SSL
394
            # (thanks to bialix)
395
            pycurl.version_info = lambda : (2,
396
                                            '7.13.2',
397
                                            462082,
398
                                            'i386-pc-win32',
399
                                            2576,
400
                                            None,
401
                                            0,
402
                                            None,
403
                                            ('ftp', 'gopher', 'telnet',
404
                                             'dict', 'ldap', 'http', 'file'),
405
                                            None,
406
                                            0,
407
                                            None)
408
            self.assertRaises(errors.DependencyNotPresent, self._transport,
409
                              'https://launchpad.net')
410
        finally:
411
            # Restore the right function
412
            pycurl.version_info = version_info_orig
2294.3.1 by Vincent Ladeuil
Fix #85305 by issuing an exception instead of a traceback.
413
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
414
415
class TestHTTPConnections(http_utils.TestCaseWithWebserver):
416
    """Test the http connections."""
417
418
    def setUp(self):
419
        http_utils.TestCaseWithWebserver.setUp(self)
420
        self.build_tree(['foo/', 'foo/bar'], line_endings='binary',
421
                        transport=self.get_transport())
422
423
    def test_http_has(self):
424
        server = self.get_readonly_server()
425
        t = self._transport(server.get_url())
426
        self.assertEqual(t.has('foo/bar'), True)
427
        self.assertEqual(len(server.logs), 1)
428
        self.assertContainsRe(server.logs[0],
429
            r'"HEAD /foo/bar HTTP/1.." (200|302) - "-" "bzr/')
430
431
    def test_http_has_not_found(self):
432
        server = self.get_readonly_server()
433
        t = self._transport(server.get_url())
434
        self.assertEqual(t.has('not-found'), False)
435
        self.assertContainsRe(server.logs[1],
436
            r'"HEAD /not-found HTTP/1.." 404 - "-" "bzr/')
437
438
    def test_http_get(self):
439
        server = self.get_readonly_server()
440
        t = self._transport(server.get_url())
441
        fp = t.get('foo/bar')
442
        self.assertEqualDiff(
443
            fp.read(),
444
            'contents of foo/bar\n')
445
        self.assertEqual(len(server.logs), 1)
446
        self.assertTrue(server.logs[0].find(
447
            '"GET /foo/bar HTTP/1.1" 200 - "-" "bzr/%s'
448
            % bzrlib.__version__) > -1)
449
450
    def test_get_smart_medium(self):
451
        # For HTTP, get_smart_medium should return the transport object.
452
        server = self.get_readonly_server()
453
        http_transport = self._transport(server.get_url())
454
        medium = http_transport.get_smart_medium()
455
        self.assertIs(medium, http_transport)
456
457
    def test_has_on_bogus_host(self):
458
        # Get a free address and don't 'accept' on it, so that we
459
        # can be sure there is no http handler there, but set a
460
        # reasonable timeout to not slow down tests too much.
461
        default_timeout = socket.getdefaulttimeout()
462
        try:
463
            socket.setdefaulttimeout(2)
464
            s = socket.socket()
465
            s.bind(('localhost', 0))
466
            t = self._transport('http://%s:%s/' % s.getsockname())
467
            self.assertRaises(errors.ConnectionError, t.has, 'foo/bar')
468
        finally:
469
            socket.setdefaulttimeout(default_timeout)
470
471
472
class TestHttpTransportRegistration(tests.TestCase):
473
    """Test registrations of various http implementations"""
474
475
    def test_http_registered(self):
476
        t = transport.get_transport('%s://foo.com/' % self._qualified_prefix)
477
        self.assertIsInstance(t, transport.Transport)
478
        self.assertIsInstance(t, self._transport)
479
480
481
class TestPost(tests.TestCase):
482
483
    def test_post_body_is_received(self):
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
484
        server = RecordingServer(expect_body_tail='end-of-body')
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
485
        server.setUp()
486
        self.addCleanup(server.tearDown)
487
        scheme = self._qualified_prefix
488
        url = '%s://%s:%s/' % (scheme, server.host, server.port)
489
        http_transport = self._transport(url)
490
        code, response = http_transport._post('abc def end-of-body')
491
        self.assertTrue(
492
            server.received_bytes.startswith('POST /.bzr/smart HTTP/1.'))
493
        self.assertTrue('content-length: 19\r' in server.received_bytes.lower())
494
        # The transport should not be assuming that the server can accept
495
        # chunked encoding the first time it connects, because HTTP/1.1, so we
496
        # check for the literal string.
497
        self.assertTrue(
498
            server.received_bytes.endswith('\r\n\r\nabc def end-of-body'))
499
500
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
501
class TestRangeHeader(tests.TestCase):
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
502
    """Test range_header method"""
503
504
    def check_header(self, value, ranges=[], tail=0):
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
505
        offsets = [ (start, end - start + 1) for start, end in ranges]
3111.1.10 by Vincent Ladeuil
Finish http parameterization, 24 auth tests failing for pycurl (not
506
        coalesce = transport.Transport._coalesce_offsets
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
507
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
508
        range_header = http.HttpTransportBase._range_header
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
509
        self.assertEqual(value, range_header(coalesced, tail))
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
510
511
    def test_range_header_single(self):
2520.2.1 by Vincent Ladeuil
First step to fix #115209 use _coalesce_offsets like other transports.
512
        self.check_header('0-9', ranges=[(0,9)])
513
        self.check_header('100-109', ranges=[(100,109)])
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
514
515
    def test_range_header_tail(self):
1786.1.36 by John Arbash Meinel
pycurl expects us to just set the range of bytes, not including bytes=
516
        self.check_header('-10', tail=10)
517
        self.check_header('-50', tail=50)
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
518
519
    def test_range_header_multi(self):
1786.1.36 by John Arbash Meinel
pycurl expects us to just set the range of bytes, not including bytes=
520
        self.check_header('0-9,100-200,300-5000',
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
521
                          ranges=[(0,9), (100, 200), (300,5000)])
522
523
    def test_range_header_mixed(self):
1786.1.36 by John Arbash Meinel
pycurl expects us to just set the range of bytes, not including bytes=
524
        self.check_header('0-9,300-5000,-50',
1786.1.28 by John Arbash Meinel
Update and add tests for the HttpTransportBase.range_header
525
                          ranges=[(0,9), (300,5000)],
526
                          tail=50)
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
527
2004.1.15 by v.ladeuil+lp at free
Better design for bogus servers. Both urllib and pycurl pass tests.
528
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
529
class TestSpecificRequestHandler(http_utils.TestCaseWithWebserver):
530
    """Tests a specific request handler.
531
3111.1.31 by Vincent Ladeuil
Review feeback.
532
    Daughter classes are expected to override _req_handler_class
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
533
    """
534
535
    # Provide a useful default
536
    _req_handler_class = http_server.TestingHTTPRequestHandler
537
538
    def create_transport_readonly_server(self):
539
        return http_server.HttpServer(self._req_handler_class,
540
                                      protocol_version=self._protocol_version)
541
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
542
    def _testing_pycurl(self):
543
        return pycurl_present and self._transport == PyCurlTransport
544
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
545
546
class WallRequestHandler(http_server.TestingHTTPRequestHandler):
547
    """Whatever request comes in, close the connection"""
548
549
    def handle_one_request(self):
550
        """Handle a single HTTP request, by abruptly closing the connection"""
551
        self.close_connection = 1
552
553
554
class TestWallServer(TestSpecificRequestHandler):
555
    """Tests exceptions during the connection phase"""
556
557
    _req_handler_class = WallRequestHandler
558
559
    def test_http_has(self):
560
        server = self.get_readonly_server()
561
        t = self._transport(server.get_url())
562
        # Unfortunately httplib (see HTTPResponse._read_status
563
        # for details) make no distinction between a closed
564
        # socket and badly formatted status line, so we can't
565
        # just test for ConnectionError, we have to test
566
        # InvalidHttpResponse too.
567
        self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
568
                          t.has, 'foo/bar')
569
570
    def test_http_get(self):
571
        server = self.get_readonly_server()
572
        t = self._transport(server.get_url())
573
        self.assertRaises((errors.ConnectionError, errors.InvalidHttpResponse),
574
                          t.get, 'foo/bar')
575
576
577
class BadStatusRequestHandler(http_server.TestingHTTPRequestHandler):
578
    """Whatever request comes in, returns a bad status"""
579
580
    def parse_request(self):
581
        """Fakes handling a single HTTP request, returns a bad status"""
582
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
583
        self.send_response(0, "Bad status")
584
        self.close_connection = 1
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
585
        return False
586
587
588
class TestBadStatusServer(TestSpecificRequestHandler):
589
    """Tests bad status from server."""
590
591
    _req_handler_class = BadStatusRequestHandler
592
593
    def test_http_has(self):
594
        server = self.get_readonly_server()
595
        t = self._transport(server.get_url())
596
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
597
598
    def test_http_get(self):
599
        server = self.get_readonly_server()
600
        t = self._transport(server.get_url())
601
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
602
603
604
class InvalidStatusRequestHandler(http_server.TestingHTTPRequestHandler):
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
605
    """Whatever request comes in, returns an invalid status"""
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
606
607
    def parse_request(self):
608
        """Fakes handling a single HTTP request, returns a bad status"""
609
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
610
        self.wfile.write("Invalid status line\r\n")
611
        return False
612
613
614
class TestInvalidStatusServer(TestBadStatusServer):
615
    """Tests invalid status from server.
616
617
    Both implementations raises the same error as for a bad status.
618
    """
619
620
    _req_handler_class = InvalidStatusRequestHandler
621
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
622
    def test_http_has(self):
623
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
3111.1.22 by Vincent Ladeuil
Rework TestingHTTPServer classes, fix test bug.
624
            raise tests.KnownFailure(
625
                'pycurl hangs if the server send back garbage')
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
626
        super(TestInvalidStatusServer, self).test_http_has()
627
628
    def test_http_get(self):
629
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
3111.1.22 by Vincent Ladeuil
Rework TestingHTTPServer classes, fix test bug.
630
            raise tests.KnownFailure(
631
                'pycurl hangs if the server send back garbage')
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
632
        super(TestInvalidStatusServer, self).test_http_get()
633
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
634
635
class BadProtocolRequestHandler(http_server.TestingHTTPRequestHandler):
636
    """Whatever request comes in, returns a bad protocol version"""
637
638
    def parse_request(self):
639
        """Fakes handling a single HTTP request, returns a bad status"""
640
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
641
        # Returns an invalid protocol version, but curl just
642
        # ignores it and those cannot be tested.
643
        self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
644
                                           404,
645
                                           'Look at my protocol version'))
646
        return False
647
648
649
class TestBadProtocolServer(TestSpecificRequestHandler):
650
    """Tests bad protocol from server."""
651
652
    _req_handler_class = BadProtocolRequestHandler
653
654
    def setUp(self):
655
        if pycurl_present and self._transport == PyCurlTransport:
656
            raise tests.TestNotApplicable(
657
                "pycurl doesn't check the protocol version")
658
        super(TestBadProtocolServer, self).setUp()
659
660
    def test_http_has(self):
661
        server = self.get_readonly_server()
662
        t = self._transport(server.get_url())
663
        self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
664
665
    def test_http_get(self):
666
        server = self.get_readonly_server()
667
        t = self._transport(server.get_url())
668
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'foo/bar')
669
670
671
class ForbiddenRequestHandler(http_server.TestingHTTPRequestHandler):
672
    """Whatever request comes in, returns a 403 code"""
673
674
    def parse_request(self):
675
        """Handle a single HTTP request, by replying we cannot handle it"""
676
        ignored = http_server.TestingHTTPRequestHandler.parse_request(self)
677
        self.send_error(403)
678
        return False
679
680
681
class TestForbiddenServer(TestSpecificRequestHandler):
682
    """Tests forbidden server"""
683
684
    _req_handler_class = ForbiddenRequestHandler
685
686
    def test_http_has(self):
687
        server = self.get_readonly_server()
688
        t = self._transport(server.get_url())
689
        self.assertRaises(errors.TransportError, t.has, 'foo/bar')
690
691
    def test_http_get(self):
692
        server = self.get_readonly_server()
693
        t = self._transport(server.get_url())
694
        self.assertRaises(errors.TransportError, t.get, 'foo/bar')
695
696
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
697
class TestRecordingServer(tests.TestCase):
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
698
699
    def test_create(self):
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
700
        server = RecordingServer(expect_body_tail=None)
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
701
        self.assertEqual('', server.received_bytes)
702
        self.assertEqual(None, server.host)
703
        self.assertEqual(None, server.port)
704
705
    def test_setUp_and_tearDown(self):
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
706
        server = RecordingServer(expect_body_tail=None)
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
707
        server.setUp()
708
        try:
709
            self.assertNotEqual(None, server.host)
710
            self.assertNotEqual(None, server.port)
711
        finally:
712
            server.tearDown()
713
        self.assertEqual(None, server.host)
714
        self.assertEqual(None, server.port)
715
716
    def test_send_receive_bytes(self):
3111.1.29 by Vincent Ladeuil
Cancel RecordingServer move, that was useless.
717
        server = RecordingServer(expect_body_tail='c')
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
718
        server.setUp()
719
        self.addCleanup(server.tearDown)
720
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
721
        sock.connect((server.host, server.port))
722
        sock.sendall('abc')
723
        self.assertEqual('HTTP/1.1 200 OK\r\n',
2091.1.1 by Martin Pool
Avoid MSG_WAITALL as it doesn't work on Windows
724
                         osutils.recv_all(sock, 4096))
2018.2.9 by Andrew Bennetts
(Andrew Bennetts, Robert Collins) Add test_http.RecordingServer, and use it to
725
        self.assertEqual('abc', server.received_bytes)
2004.1.29 by v.ladeuil+lp at free
New tests for http range requests handling.
726
727
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
728
class TestRangeRequestServer(TestSpecificRequestHandler):
729
    """Tests readv requests against server.
730
731
    We test against default "normal" server.
732
    """
733
734
    def setUp(self):
735
        super(TestRangeRequestServer, self).setUp()
736
        self.build_tree_contents([('a', '0123456789')],)
737
738
    def test_readv(self):
739
        server = self.get_readonly_server()
740
        t = self._transport(server.get_url())
741
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
742
        self.assertEqual(l[0], (0, '0'))
743
        self.assertEqual(l[1], (1, '1'))
744
        self.assertEqual(l[2], (3, '34'))
745
        self.assertEqual(l[3], (9, '9'))
746
747
    def test_readv_out_of_order(self):
748
        server = self.get_readonly_server()
749
        t = self._transport(server.get_url())
750
        l = list(t.readv('a', ((1, 1), (9, 1), (0, 1), (3, 2))))
751
        self.assertEqual(l[0], (1, '1'))
752
        self.assertEqual(l[1], (9, '9'))
753
        self.assertEqual(l[2], (0, '0'))
754
        self.assertEqual(l[3], (3, '34'))
755
756
    def test_readv_invalid_ranges(self):
757
        server = self.get_readonly_server()
758
        t = self._transport(server.get_url())
759
760
        # This is intentionally reading off the end of the file
761
        # since we are sure that it cannot get there
762
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
763
                              t.readv, 'a', [(1,1), (8,10)])
764
765
        # This is trying to seek past the end of the file, it should
766
        # also raise a special error
767
        self.assertListRaises((errors.InvalidRange, errors.ShortReadvError,),
768
                              t.readv, 'a', [(12,2)])
769
770
    def test_readv_multiple_get_requests(self):
771
        server = self.get_readonly_server()
772
        t = self._transport(server.get_url())
773
        # force transport to issue multiple requests
774
        t._max_readv_combine = 1
775
        t._max_get_ranges = 1
776
        l = list(t.readv('a', ((0, 1), (1, 1), (3, 2), (9, 1))))
777
        self.assertEqual(l[0], (0, '0'))
778
        self.assertEqual(l[1], (1, '1'))
779
        self.assertEqual(l[2], (3, '34'))
780
        self.assertEqual(l[3], (9, '9'))
781
        # The server should have issued 4 requests
782
        self.assertEqual(4, server.GET_request_nb)
783
784
    def test_readv_get_max_size(self):
785
        server = self.get_readonly_server()
786
        t = self._transport(server.get_url())
787
        # force transport to issue multiple requests by limiting the number of
788
        # bytes by request. Note that this apply to coalesced offsets only, a
3111.1.28 by Vincent Ladeuil
Fix the multi-ranges http server and add tests.
789
        # single range will keep its size even if bigger than the limit.
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
790
        t._get_max_size = 2
791
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
792
        self.assertEqual(l[0], (0, '0'))
793
        self.assertEqual(l[1], (1, '1'))
794
        self.assertEqual(l[2], (2, '2345'))
795
        self.assertEqual(l[3], (6, '6789'))
796
        # The server should have issued 3 requests
797
        self.assertEqual(3, server.GET_request_nb)
798
3111.1.28 by Vincent Ladeuil
Fix the multi-ranges http server and add tests.
799
    def test_complete_readv_leave_pipe_clean(self):
800
        server = self.get_readonly_server()
801
        t = self._transport(server.get_url())
802
        # force transport to issue multiple requests
803
        t._get_max_size = 2
804
        l = list(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
805
        # The server should have issued 3 requests
806
        self.assertEqual(3, server.GET_request_nb)
807
        self.assertEqual('0123456789', t.get_bytes('a'))
808
        self.assertEqual(4, server.GET_request_nb)
809
810
    def test_incomplete_readv_leave_pipe_clean(self):
811
        server = self.get_readonly_server()
812
        t = self._transport(server.get_url())
813
        # force transport to issue multiple requests
814
        t._get_max_size = 2
815
        # Don't collapse readv results into a list so that we leave unread
816
        # bytes on the socket
817
        ireadv = iter(t.readv('a', ((0, 1), (1, 1), (2, 4), (6, 4))))
818
        self.assertEqual((0, '0'), ireadv.next())
819
        # The server should have issued one request so far 
820
        self.assertEqual(1, server.GET_request_nb)
821
        self.assertEqual('0123456789', t.get_bytes('a'))
822
        # get_bytes issued an additional request, the readv pending ones are
823
        # lost
824
        self.assertEqual(2, server.GET_request_nb)
825
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
826
827
class SingleRangeRequestHandler(http_server.TestingHTTPRequestHandler):
828
    """Always reply to range request as if they were single.
829
830
    Don't be explicit about it, just to annoy the clients.
831
    """
832
833
    def get_multiple_ranges(self, file, file_size, ranges):
834
        """Answer as if it was a single range request and ignores the rest"""
835
        (start, end) = ranges[0]
836
        return self.get_single_range(file, file_size, start, end)
837
838
839
class TestSingleRangeRequestServer(TestRangeRequestServer):
840
    """Test readv against a server which accept only single range requests"""
841
842
    _req_handler_class = SingleRangeRequestHandler
843
844
845
class SingleOnlyRangeRequestHandler(http_server.TestingHTTPRequestHandler):
846
    """Only reply to simple range requests, errors out on multiple"""
847
848
    def get_multiple_ranges(self, file, file_size, ranges):
849
        """Refuses the multiple ranges request"""
850
        if len(ranges) > 1:
851
            file.close()
852
            self.send_error(416, "Requested range not satisfiable")
853
            return
854
        (start, end) = ranges[0]
855
        return self.get_single_range(file, file_size, start, end)
856
857
858
class TestSingleOnlyRangeRequestServer(TestRangeRequestServer):
859
    """Test readv against a server which only accept single range requests"""
860
861
    _req_handler_class = SingleOnlyRangeRequestHandler
862
863
864
class NoRangeRequestHandler(http_server.TestingHTTPRequestHandler):
865
    """Ignore range requests without notice"""
866
867
    def do_GET(self):
868
        # Update the statistics
869
        self.server.test_case_server.GET_request_nb += 1
870
        # Just bypass the range handling done by TestingHTTPRequestHandler
871
        return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
872
873
874
class TestNoRangeRequestServer(TestRangeRequestServer):
875
    """Test readv against a server which do not accept range requests"""
876
877
    _req_handler_class = NoRangeRequestHandler
878
879
3111.1.28 by Vincent Ladeuil
Fix the multi-ranges http server and add tests.
880
class MultipleRangeWithoutContentLengthRequestHandler(
881
    http_server.TestingHTTPRequestHandler):
882
    """Reply to multiple range requests without content length header."""
883
884
    def get_multiple_ranges(self, file, file_size, ranges):
885
        self.send_response(206)
886
        self.send_header('Accept-Ranges', 'bytes')
887
        boundary = "%d" % random.randint(0,0x7FFFFFFF)
888
        self.send_header("Content-Type",
889
                         "multipart/byteranges; boundary=%s" % boundary)
890
        self.end_headers()
891
        for (start, end) in ranges:
892
            self.wfile.write("--%s\r\n" % boundary)
893
            self.send_header("Content-type", 'application/octet-stream')
894
            self.send_header("Content-Range", "bytes %d-%d/%d" % (start,
895
                                                                  end,
896
                                                                  file_size))
897
            self.end_headers()
898
            self.send_range_content(file, start, end - start + 1)
899
        # Final boundary
900
        self.wfile.write("--%s\r\n" % boundary)
901
902
903
class TestMultipleRangeWithoutContentLengthServer(TestRangeRequestServer):
904
905
    _req_handler_class = MultipleRangeWithoutContentLengthRequestHandler
906
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
907
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
908
    """Errors out when range specifiers exceed the limit"""
909
910
    def get_multiple_ranges(self, file, file_size, ranges):
911
        """Refuses the multiple ranges request"""
912
        tcs = self.server.test_case_server
913
        if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
914
            file.close()
915
            # Emulate apache behavior
916
            self.send_error(400, "Bad Request")
917
            return
918
        return http_server.TestingHTTPRequestHandler.get_multiple_ranges(
919
            self, file, file_size, ranges)
920
921
922
class LimitedRangeHTTPServer(http_server.HttpServer):
923
    """An HttpServer erroring out on requests with too much range specifiers"""
924
925
    def __init__(self, request_handler=LimitedRangeRequestHandler,
926
                 protocol_version=None,
927
                 range_limit=None):
928
        http_server.HttpServer.__init__(self, request_handler,
929
                                        protocol_version=protocol_version)
930
        self.range_limit = range_limit
931
932
933
class TestLimitedRangeRequestServer(http_utils.TestCaseWithWebserver):
934
    """Tests readv requests against a server erroring out on too much ranges."""
935
3111.1.22 by Vincent Ladeuil
Rework TestingHTTPServer classes, fix test bug.
936
    # Requests with more range specifiers will error out
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
937
    range_limit = 3
938
939
    def create_transport_readonly_server(self):
940
        return LimitedRangeHTTPServer(range_limit=self.range_limit,
941
                                      protocol_version=self._protocol_version)
942
943
    def get_transport(self):
944
        return self._transport(self.get_readonly_server().get_url())
945
946
    def setUp(self):
947
        http_utils.TestCaseWithWebserver.setUp(self)
948
        # We need to manipulate ranges that correspond to real chunks in the
949
        # response, so we build a content appropriately.
950
        filler = ''.join(['abcdefghij' for x in range(102)])
951
        content = ''.join(['%04d' % v + filler for v in range(16)])
952
        self.build_tree_contents([('a', content)],)
953
954
    def test_few_ranges(self):
955
        t = self.get_transport()
956
        l = list(t.readv('a', ((0, 4), (1024, 4), )))
957
        self.assertEqual(l[0], (0, '0000'))
958
        self.assertEqual(l[1], (1024, '0001'))
959
        self.assertEqual(1, self.get_readonly_server().GET_request_nb)
960
961
    def test_more_ranges(self):
962
        t = self.get_transport()
963
        l = list(t.readv('a', ((0, 4), (1024, 4), (4096, 4), (8192, 4))))
964
        self.assertEqual(l[0], (0, '0000'))
965
        self.assertEqual(l[1], (1024, '0001'))
966
        self.assertEqual(l[2], (4096, '0004'))
967
        self.assertEqual(l[3], (8192, '0008'))
968
        # The server will refuse to serve the first request (too much ranges),
969
        # a second request will succeeds.
970
        self.assertEqual(2, self.get_readonly_server().GET_request_nb)
971
972
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
973
class TestHttpProxyWhiteBox(tests.TestCase):
2298.7.1 by Vincent Ladeuil
Fix bug #87765: proxy env variables without scheme should cause
974
    """Whitebox test proxy http authorization.
975
2420.1.3 by Vincent Ladeuil
Implement http proxy basic authentication.
976
    Only the urllib implementation is tested here.
2298.7.1 by Vincent Ladeuil
Fix bug #87765: proxy env variables without scheme should cause
977
    """
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
978
979
    def setUp(self):
3052.3.2 by Vincent Ladeuil
Add tests and fix trivial bugs and other typos.
980
        tests.TestCase.setUp(self)
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
981
        self._old_env = {}
982
983
    def tearDown(self):
984
        self._restore_env()
985
986
    def _install_env(self, env):
987
        for name, value in env.iteritems():
2420.1.2 by Vincent Ladeuil
Define tests for http proxy basic authentication. They fail.
988
            self._old_env[name] = osutils.set_or_unset_env(name, value)
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
989
990
    def _restore_env(self):
991
        for name, value in self._old_env.iteritems():
992
            osutils.set_or_unset_env(name, value)
993
994
    def _proxied_request(self):
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
995
        handler = _urllib2_wrappers.ProxyHandler()
996
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
997
        handler.set_proxy(request, 'http')
998
        return request
999
1000
    def test_empty_user(self):
1001
        self._install_env({'http_proxy': 'http://bar.com'})
1002
        request = self._proxied_request()
1003
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
1004
2298.7.1 by Vincent Ladeuil
Fix bug #87765: proxy env variables without scheme should cause
1005
    def test_invalid_proxy(self):
1006
        """A proxy env variable without scheme"""
1007
        self._install_env({'http_proxy': 'host:1234'})
1008
        self.assertRaises(errors.InvalidURL, self._proxied_request)
2273.2.2 by v.ladeuil+lp at free
Really fix bug #83954, with tests.
1009
1010
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1011
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
1012
    """Tests proxy server.
1013
1014
    Be aware that we do not setup a real proxy here. Instead, we
1015
    check that the *connection* goes through the proxy by serving
1016
    different content (the faked proxy server append '-proxied'
1017
    to the file names).
1018
    """
1019
1020
    # FIXME: We don't have an https server available, so we don't
1021
    # test https connections.
1022
1023
    def setUp(self):
1024
        super(TestProxyHttpServer, self).setUp()
1025
        self.build_tree_contents([('foo', 'contents of foo\n'),
1026
                                  ('foo-proxied', 'proxied contents of foo\n')])
1027
        # Let's setup some attributes for tests
1028
        self.server = self.get_readonly_server()
1029
        self.proxy_address = '%s:%d' % (self.server.host, self.server.port)
1030
        if self._testing_pycurl():
1031
            # Oh my ! pycurl does not check for the port as part of
1032
            # no_proxy :-( So we just test the host part
1033
            self.no_proxy_host = 'localhost'
1034
        else:
1035
            self.no_proxy_host = self.proxy_address
1036
        # The secondary server is the proxy
1037
        self.proxy = self.get_secondary_server()
1038
        self.proxy_url = self.proxy.get_url()
1039
        self._old_env = {}
1040
1041
    def _testing_pycurl(self):
1042
        return pycurl_present and self._transport == PyCurlTransport
1043
1044
    def create_transport_secondary_server(self):
1045
        """Creates an http server that will serve files with
1046
        '-proxied' appended to their names.
1047
        """
1048
        return http_utils.ProxyServer(protocol_version=self._protocol_version)
1049
1050
    def _install_env(self, env):
1051
        for name, value in env.iteritems():
1052
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1053
1054
    def _restore_env(self):
1055
        for name, value in self._old_env.iteritems():
1056
            osutils.set_or_unset_env(name, value)
1057
1058
    def proxied_in_env(self, env):
1059
        self._install_env(env)
1060
        url = self.server.get_url()
1061
        t = self._transport(url)
1062
        try:
1063
            self.assertEqual(t.get('foo').read(), 'proxied contents of foo\n')
1064
        finally:
1065
            self._restore_env()
1066
1067
    def not_proxied_in_env(self, env):
1068
        self._install_env(env)
1069
        url = self.server.get_url()
1070
        t = self._transport(url)
1071
        try:
1072
            self.assertEqual(t.get('foo').read(), 'contents of foo\n')
1073
        finally:
1074
            self._restore_env()
1075
1076
    def test_http_proxy(self):
1077
        self.proxied_in_env({'http_proxy': self.proxy_url})
1078
1079
    def test_HTTP_PROXY(self):
1080
        if self._testing_pycurl():
1081
            # pycurl does not check HTTP_PROXY for security reasons
1082
            # (for use in a CGI context that we do not care
1083
            # about. Should we ?)
1084
            raise tests.TestNotApplicable(
1085
                'pycurl does not check HTTP_PROXY for security reasons')
1086
        self.proxied_in_env({'HTTP_PROXY': self.proxy_url})
1087
1088
    def test_all_proxy(self):
1089
        self.proxied_in_env({'all_proxy': self.proxy_url})
1090
1091
    def test_ALL_PROXY(self):
1092
        self.proxied_in_env({'ALL_PROXY': self.proxy_url})
1093
1094
    def test_http_proxy_with_no_proxy(self):
1095
        self.not_proxied_in_env({'http_proxy': self.proxy_url,
1096
                                 'no_proxy': self.no_proxy_host})
1097
1098
    def test_HTTP_PROXY_with_NO_PROXY(self):
1099
        if self._testing_pycurl():
1100
            raise tests.TestNotApplicable(
1101
                'pycurl does not check HTTP_PROXY for security reasons')
1102
        self.not_proxied_in_env({'HTTP_PROXY': self.proxy_url,
1103
                                 'NO_PROXY': self.no_proxy_host})
1104
1105
    def test_all_proxy_with_no_proxy(self):
1106
        self.not_proxied_in_env({'all_proxy': self.proxy_url,
1107
                                 'no_proxy': self.no_proxy_host})
1108
1109
    def test_ALL_PROXY_with_NO_PROXY(self):
1110
        self.not_proxied_in_env({'ALL_PROXY': self.proxy_url,
1111
                                 'NO_PROXY': self.no_proxy_host})
1112
1113
    def test_http_proxy_without_scheme(self):
1114
        if self._testing_pycurl():
1115
            # pycurl *ignores* invalid proxy env variables. If that ever change
1116
            # in the future, this test will fail indicating that pycurl do not
1117
            # ignore anymore such variables.
1118
            self.not_proxied_in_env({'http_proxy': self.proxy_address})
1119
        else:
1120
            self.assertRaises(errors.InvalidURL,
1121
                              self.proxied_in_env,
1122
                              {'http_proxy': self.proxy_address})
1123
1124
1125
class TestRanges(http_utils.TestCaseWithWebserver):
1126
    """Test the Range header in GET methods."""
1127
1128
    def setUp(self):
1129
        http_utils.TestCaseWithWebserver.setUp(self)
1130
        self.build_tree_contents([('a', '0123456789')],)
1131
        server = self.get_readonly_server()
1132
        self.transport = self._transport(server.get_url())
1133
3111.1.22 by Vincent Ladeuil
Rework TestingHTTPServer classes, fix test bug.
1134
    def create_transport_readonly_server(self):
1135
        return http_server.HttpServer(protocol_version=self._protocol_version)
1136
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1137
    def _file_contents(self, relpath, ranges):
1138
        offsets = [ (start, end - start + 1) for start, end in ranges]
1139
        coalesce = self.transport._coalesce_offsets
1140
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
1141
        code, data = self.transport._get(relpath, coalesced)
1142
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1143
        for start, end in ranges:
1144
            data.seek(start)
1145
            yield data.read(end - start + 1)
1146
1147
    def _file_tail(self, relpath, tail_amount):
1148
        code, data = self.transport._get(relpath, [], tail_amount)
1149
        self.assertTrue(code in (200, 206),'_get returns: %d' % code)
1150
        data.seek(-tail_amount, 2)
1151
        return data.read(tail_amount)
1152
1153
    def test_range_header(self):
1154
        # Valid ranges
1155
        map(self.assertEqual,['0', '234'],
1156
            list(self._file_contents('a', [(0,0), (2,4)])),)
1157
1158
    def test_range_header_tail(self):
1159
        self.assertEqual('789', self._file_tail('a', 3))
1160
1161
    def test_syntactically_invalid_range_header(self):
1162
        self.assertListRaises(errors.InvalidHttpRange,
1163
                          self._file_contents, 'a', [(4, 3)])
1164
1165
    def test_semantically_invalid_range_header(self):
1166
        self.assertListRaises(errors.InvalidHttpRange,
1167
                          self._file_contents, 'a', [(42, 128)])
1168
1169
1170
class TestHTTPRedirections(http_utils.TestCaseWithRedirectedWebserver):
1171
    """Test redirection between http servers."""
1172
1173
    def create_transport_secondary_server(self):
1174
        """Create the secondary server redirecting to the primary server"""
1175
        new = self.get_readonly_server()
1176
1177
        redirecting = http_utils.HTTPServerRedirecting(
1178
            protocol_version=self._protocol_version)
1179
        redirecting.redirect_to(new.host, new.port)
1180
        return redirecting
1181
1182
    def setUp(self):
1183
        super(TestHTTPRedirections, self).setUp()
1184
        self.build_tree_contents([('a', '0123456789'),
1185
                                  ('bundle',
1186
                                  '# Bazaar revision bundle v0.9\n#\n')
1187
                                  ],)
1188
1189
        self.old_transport = self._transport(self.old_server.get_url())
1190
1191
    def test_redirected(self):
1192
        self.assertRaises(errors.RedirectRequested, self.old_transport.get, 'a')
1193
        t = self._transport(self.new_server.get_url())
1194
        self.assertEqual('0123456789', t.get('a').read())
1195
1196
    def test_read_redirected_bundle_from_url(self):
1197
        from bzrlib.bundle import read_bundle_from_url
1198
        url = self.old_transport.abspath('bundle')
1199
        bundle = read_bundle_from_url(url)
1200
        # If read_bundle_from_url was successful we get an empty bundle
1201
        self.assertEqual([], bundle.revisions)
1202
1203
1204
class RedirectedRequest(_urllib2_wrappers.Request):
1205
    """Request following redirections. """
1206
1207
    init_orig = _urllib2_wrappers.Request.__init__
1208
1209
    def __init__(self, method, url, *args, **kwargs):
1210
        """Constructor.
1211
1212
        """
1213
        # Since the tests using this class will replace
1214
        # _urllib2_wrappers.Request, we can't just call the base class __init__
1215
        # or we'll loop.
1216
        RedirectedRequest.init_orig(self, method, url, args, kwargs)
1217
        self.follow_redirections = True
1218
1219
1220
class TestHTTPSilentRedirections(http_utils.TestCaseWithRedirectedWebserver):
1221
    """Test redirections.
1222
1223
    http implementations do not redirect silently anymore (they
1224
    do not redirect at all in fact). The mechanism is still in
1225
    place at the _urllib2_wrappers.Request level and these tests
1226
    exercise it.
1227
1228
    For the pycurl implementation
1229
    the redirection have been deleted as we may deprecate pycurl
1230
    and I have no place to keep a working implementation.
1231
    -- vila 20070212
1232
    """
1233
1234
    def setUp(self):
1235
        if pycurl_present and self._transport == PyCurlTransport:
1236
            raise tests.TestNotApplicable(
1237
                "pycurl doesn't redirect silently annymore")
1238
        super(TestHTTPSilentRedirections, self).setUp()
1239
        self.setup_redirected_request()
1240
        self.addCleanup(self.cleanup_redirected_request)
1241
        self.build_tree_contents([('a','a'),
1242
                                  ('1/',),
1243
                                  ('1/a', 'redirected once'),
1244
                                  ('2/',),
1245
                                  ('2/a', 'redirected twice'),
1246
                                  ('3/',),
1247
                                  ('3/a', 'redirected thrice'),
1248
                                  ('4/',),
1249
                                  ('4/a', 'redirected 4 times'),
1250
                                  ('5/',),
1251
                                  ('5/a', 'redirected 5 times'),
1252
                                  ],)
1253
1254
        self.old_transport = self._transport(self.old_server.get_url())
1255
1256
    def setup_redirected_request(self):
1257
        self.original_class = _urllib2_wrappers.Request
1258
        _urllib2_wrappers.Request = RedirectedRequest
1259
1260
    def cleanup_redirected_request(self):
1261
        _urllib2_wrappers.Request = self.original_class
1262
1263
    def create_transport_secondary_server(self):
1264
        """Create the secondary server, redirections are defined in the tests"""
1265
        return http_utils.HTTPServerRedirecting(
1266
            protocol_version=self._protocol_version)
1267
1268
    def test_one_redirection(self):
1269
        t = self.old_transport
1270
1271
        req = RedirectedRequest('GET', t.abspath('a'))
1272
        req.follow_redirections = True
1273
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1274
                                       self.new_server.port)
1275
        self.old_server.redirections = \
1276
            [('(.*)', r'%s/1\1' % (new_prefix), 301),]
1277
        self.assertEquals('redirected once',t._perform(req).read())
1278
1279
    def test_five_redirections(self):
1280
        t = self.old_transport
1281
1282
        req = RedirectedRequest('GET', t.abspath('a'))
1283
        req.follow_redirections = True
1284
        old_prefix = 'http://%s:%s' % (self.old_server.host,
1285
                                       self.old_server.port)
1286
        new_prefix = 'http://%s:%s' % (self.new_server.host,
1287
                                       self.new_server.port)
3111.1.20 by Vincent Ladeuil
Make all the test pass. Looks like we are HTTP/1.1 compliant.
1288
        self.old_server.redirections = [
1289
            ('/1(.*)', r'%s/2\1' % (old_prefix), 302),
1290
            ('/2(.*)', r'%s/3\1' % (old_prefix), 303),
1291
            ('/3(.*)', r'%s/4\1' % (old_prefix), 307),
1292
            ('/4(.*)', r'%s/5\1' % (new_prefix), 301),
1293
            ('(/[^/]+)', r'%s/1\1' % (old_prefix), 301),
1294
            ]
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1295
        self.assertEquals('redirected 5 times',t._perform(req).read())
1296
1297
1298
class TestDoCatchRedirections(http_utils.TestCaseWithRedirectedWebserver):
1299
    """Test transport.do_catching_redirections."""
1300
1301
    def setUp(self):
1302
        super(TestDoCatchRedirections, self).setUp()
1303
        self.build_tree_contents([('a', '0123456789'),],)
1304
1305
        self.old_transport = self._transport(self.old_server.get_url())
1306
1307
    def get_a(self, transport):
1308
        return transport.get('a')
1309
1310
    def test_no_redirection(self):
1311
        t = self._transport(self.new_server.get_url())
1312
1313
        # We use None for redirected so that we fail if redirected
1314
        self.assertEquals('0123456789',
1315
                          transport.do_catching_redirections(
1316
                self.get_a, t, None).read())
1317
1318
    def test_one_redirection(self):
1319
        self.redirections = 0
1320
1321
        def redirected(transport, exception, redirection_notice):
1322
            self.redirections += 1
1323
            dir, file = urlutils.split(exception.target)
1324
            return self._transport(dir)
1325
1326
        self.assertEquals('0123456789',
1327
                          transport.do_catching_redirections(
1328
                self.get_a, self.old_transport, redirected).read())
1329
        self.assertEquals(1, self.redirections)
1330
1331
    def test_redirection_loop(self):
1332
1333
        def redirected(transport, exception, redirection_notice):
1334
            # By using the redirected url as a base dir for the
1335
            # *old* transport, we create a loop: a => a/a =>
1336
            # a/a/a
1337
            return self.old_transport.clone(exception.target)
1338
1339
        self.assertRaises(errors.TooManyRedirections,
1340
                          transport.do_catching_redirections,
1341
                          self.get_a, self.old_transport, redirected)
1342
1343
1344
class TestAuth(http_utils.TestCaseWithWebserver):
1345
    """Test authentication scheme"""
1346
1347
    _auth_header = 'Authorization'
1348
    _password_prompt_prefix = ''
1349
1350
    def setUp(self):
1351
        super(TestAuth, self).setUp()
1352
        self.server = self.get_readonly_server()
1353
        self.build_tree_contents([('a', 'contents of a\n'),
1354
                                  ('b', 'contents of b\n'),])
1355
1356
    def create_transport_readonly_server(self):
1357
        if self._auth_scheme == 'basic':
1358
            server = http_utils.HTTPBasicAuthServer(
1359
                protocol_version=self._protocol_version)
1360
        else:
1361
            if self._auth_scheme != 'digest':
1362
                raise AssertionError('Unknown auth scheme: %r'
1363
                                     % self._auth_scheme)
1364
            server = http_utils.HTTPDigestAuthServer(
1365
                protocol_version=self._protocol_version)
1366
        return server
1367
1368
    def _testing_pycurl(self):
1369
        return pycurl_present and self._transport == PyCurlTransport
1370
1371
    def get_user_url(self, user=None, password=None):
1372
        """Build an url embedding user and password"""
1373
        url = '%s://' % self.server._url_protocol
1374
        if user is not None:
1375
            url += user
1376
            if password is not None:
1377
                url += ':' + password
1378
            url += '@'
1379
        url += '%s:%s/' % (self.server.host, self.server.port)
1380
        return url
1381
1382
    def get_user_transport(self, user=None, password=None):
1383
        return self._transport(self.get_user_url(user, password))
1384
1385
    def test_no_user(self):
1386
        self.server.add_user('joe', 'foo')
1387
        t = self.get_user_transport()
1388
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1389
        # Only one 'Authentication Required' error should occur
1390
        self.assertEqual(1, self.server.auth_required_errors)
1391
1392
    def test_empty_pass(self):
1393
        self.server.add_user('joe', '')
1394
        t = self.get_user_transport('joe', '')
1395
        self.assertEqual('contents of a\n', t.get('a').read())
1396
        # Only one 'Authentication Required' error should occur
1397
        self.assertEqual(1, self.server.auth_required_errors)
1398
1399
    def test_user_pass(self):
1400
        self.server.add_user('joe', 'foo')
1401
        t = self.get_user_transport('joe', 'foo')
1402
        self.assertEqual('contents of a\n', t.get('a').read())
1403
        # Only one 'Authentication Required' error should occur
1404
        self.assertEqual(1, self.server.auth_required_errors)
1405
1406
    def test_unknown_user(self):
1407
        self.server.add_user('joe', 'foo')
1408
        t = self.get_user_transport('bill', 'foo')
1409
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1410
        # Two 'Authentication Required' errors should occur (the
1411
        # initial 'who are you' and 'I don't know you, who are
1412
        # you').
1413
        self.assertEqual(2, self.server.auth_required_errors)
1414
1415
    def test_wrong_pass(self):
1416
        self.server.add_user('joe', 'foo')
1417
        t = self.get_user_transport('joe', 'bar')
1418
        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
1419
        # Two 'Authentication Required' errors should occur (the
1420
        # initial 'who are you' and 'this is not you, who are you')
1421
        self.assertEqual(2, self.server.auth_required_errors)
1422
1423
    def test_prompt_for_password(self):
1424
        if self._testing_pycurl():
1425
            raise tests.TestNotApplicable(
1426
                'pycurl cannot prompt, it handles auth by embedding'
1427
                ' user:pass in urls only')
1428
1429
        self.server.add_user('joe', 'foo')
1430
        t = self.get_user_transport('joe', None)
1431
        stdout = tests.StringIOWrapper()
1432
        ui.ui_factory = tests.TestUIFactory(stdin='foo\n', stdout=stdout)
1433
        self.assertEqual('contents of a\n',t.get('a').read())
1434
        # stdin should be empty
1435
        self.assertEqual('', ui.ui_factory.stdin.readline())
1436
        self._check_password_prompt(t._unqualified_scheme, 'joe',
1437
                                    stdout.getvalue())
1438
        # And we shouldn't prompt again for a different request
1439
        # against the same transport.
1440
        self.assertEqual('contents of b\n',t.get('b').read())
1441
        t2 = t.clone()
1442
        # And neither against a clone
1443
        self.assertEqual('contents of b\n',t2.get('b').read())
1444
        # Only one 'Authentication Required' error should occur
1445
        self.assertEqual(1, self.server.auth_required_errors)
1446
1447
    def _check_password_prompt(self, scheme, user, actual_prompt):
1448
        expected_prompt = (self._password_prompt_prefix
1449
                           + ("%s %s@%s:%d, Realm: '%s' password: "
1450
                              % (scheme.upper(),
1451
                                 user, self.server.host, self.server.port,
1452
                                 self.server.auth_realm)))
1453
        self.assertEquals(expected_prompt, actual_prompt)
1454
1455
    def test_no_prompt_for_password_when_using_auth_config(self):
1456
        if self._testing_pycurl():
1457
            raise tests.TestNotApplicable(
1458
                'pycurl does not support authentication.conf'
1459
                ' since it cannot prompt')
1460
1461
        user =' joe'
1462
        password = 'foo'
1463
        stdin_content = 'bar\n'  # Not the right password
1464
        self.server.add_user(user, password)
1465
        t = self.get_user_transport(user, None)
1466
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1467
                                            stdout=tests.StringIOWrapper())
1468
        # Create a minimal config file with the right password
1469
        conf = config.AuthenticationConfig()
1470
        conf._get_config().update(
1471
            {'httptest': {'scheme': 'http', 'port': self.server.port,
1472
                          'user': user, 'password': password}})
1473
        conf._save()
1474
        # Issue a request to the server to connect
1475
        self.assertEqual('contents of a\n',t.get('a').read())
1476
        # stdin should have  been left untouched
1477
        self.assertEqual(stdin_content, ui.ui_factory.stdin.readline())
1478
        # Only one 'Authentication Required' error should occur
1479
        self.assertEqual(1, self.server.auth_required_errors)
1480
3111.1.26 by Vincent Ladeuil
Re-add a test lost in refactoring.
1481
    def test_changing_nonce(self):
1482
        if self._auth_scheme != 'digest':
1483
            raise tests.TestNotApplicable('HTTP auth digest only test')
1484
        if self._testing_pycurl():
1485
            raise tests.KnownFailure(
1486
                'pycurl does not handle a nonce change')
1487
        self.server.add_user('joe', 'foo')
1488
        t = self.get_user_transport('joe', 'foo')
1489
        self.assertEqual('contents of a\n', t.get('a').read())
1490
        self.assertEqual('contents of b\n', t.get('b').read())
1491
        # Only one 'Authentication Required' error should have
1492
        # occured so far
1493
        self.assertEqual(1, self.server.auth_required_errors)
1494
        # The server invalidates the current nonce
1495
        self.server.auth_nonce = self.server.auth_nonce + '. No, now!'
1496
        self.assertEqual('contents of a\n', t.get('a').read())
1497
        # Two 'Authentication Required' errors should occur (the
1498
        # initial 'who are you' and a second 'who are you' with the new nonce)
1499
        self.assertEqual(2, self.server.auth_required_errors)
1500
3111.1.19 by Vincent Ladeuil
Merge back test_http_implementations.pc into test_http.py.
1501
1502
1503
class TestProxyAuth(TestAuth):
1504
    """Test proxy authentication schemes."""
1505
1506
    _auth_header = 'Proxy-authorization'
1507
    _password_prompt_prefix='Proxy '
1508
1509
    def setUp(self):
1510
        super(TestProxyAuth, self).setUp()
1511
        self._old_env = {}
1512
        self.addCleanup(self._restore_env)
1513
        # Override the contents to avoid false positives
1514
        self.build_tree_contents([('a', 'not proxied contents of a\n'),
1515
                                  ('b', 'not proxied contents of b\n'),
1516
                                  ('a-proxied', 'contents of a\n'),
1517
                                  ('b-proxied', 'contents of b\n'),
1518
                                  ])
1519
1520
    def create_transport_readonly_server(self):
1521
        if self._auth_scheme == 'basic':
1522
            server = http_utils.ProxyBasicAuthServer(
1523
                protocol_version=self._protocol_version)
1524
        else:
1525
            if self._auth_scheme != 'digest':
1526
                raise AssertionError('Unknown auth scheme: %r'
1527
                                     % self._auth_scheme)
1528
            server = http_utils.ProxyDigestAuthServer(
1529
                protocol_version=self._protocol_version)
1530
        return server
1531
1532
    def get_user_transport(self, user=None, password=None):
1533
        self._install_env({'all_proxy': self.get_user_url(user, password)})
1534
        return self._transport(self.server.get_url())
1535
1536
    def _install_env(self, env):
1537
        for name, value in env.iteritems():
1538
            self._old_env[name] = osutils.set_or_unset_env(name, value)
1539
1540
    def _restore_env(self):
1541
        for name, value in self._old_env.iteritems():
1542
            osutils.set_or_unset_env(name, value)
1543
1544
    def test_empty_pass(self):
1545
        if self._testing_pycurl():
1546
            import pycurl
1547
            if pycurl.version_info()[1] < '7.16.0':
1548
                raise tests.KnownFailure(
1549
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
1550
        super(TestProxyAuth, self).test_empty_pass()
1551
3111.1.25 by Vincent Ladeuil
Fix the smart server failing test and use it against protocol combinations.
1552
1553
class SampleSocket(object):
1554
    """A socket-like object for use in testing the HTTP request handler."""
1555
1556
    def __init__(self, socket_read_content):
1557
        """Constructs a sample socket.
1558
1559
        :param socket_read_content: a byte sequence
1560
        """
1561
        # Use plain python StringIO so we can monkey-patch the close method to
1562
        # not discard the contents.
1563
        from StringIO import StringIO
1564
        self.readfile = StringIO(socket_read_content)
1565
        self.writefile = StringIO()
1566
        self.writefile.close = lambda: None
1567
1568
    def makefile(self, mode='r', bufsize=None):
1569
        if 'r' in mode:
1570
            return self.readfile
1571
        else:
1572
            return self.writefile
1573
1574
1575
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1576
1577
    def setUp(self):
1578
        super(SmartHTTPTunnellingTest, self).setUp()
1579
        # We use the VFS layer as part of HTTP tunnelling tests.
1580
        self._captureVar('BZR_NO_SMART_VFS', None)
1581
        self.transport_readonly_server = http_utils.HTTPServerWithSmarts
1582
1583
    def create_transport_readonly_server(self):
1584
        return http_utils.HTTPServerWithSmarts(
1585
            protocol_version=self._protocol_version)
1586
1587
    def test_bulk_data(self):
1588
        # We should be able to send and receive bulk data in a single message.
1589
        # The 'readv' command in the smart protocol both sends and receives
1590
        # bulk data, so we use that.
1591
        self.build_tree(['data-file'])
1592
        http_server = self.get_readonly_server()
1593
        http_transport = self._transport(http_server.get_url())
1594
        medium = http_transport.get_smart_medium()
1595
        # Since we provide the medium, the url below will be mostly ignored
1596
        # during the test, as long as the path is '/'.
1597
        remote_transport = remote.RemoteTransport('bzr://fake_host/',
1598
                                                  medium=medium)
1599
        self.assertEqual(
1600
            [(0, "c")], list(remote_transport.readv("data-file", [(0,1)])))
1601
1602
    def test_http_send_smart_request(self):
1603
1604
        post_body = 'hello\n'
1605
        expected_reply_body = 'ok\x012\n'
1606
1607
        http_server = self.get_readonly_server()
1608
        http_transport = self._transport(http_server.get_url())
1609
        medium = http_transport.get_smart_medium()
1610
        response = medium.send_http_smart_request(post_body)
1611
        reply_body = response.read()
1612
        self.assertEqual(expected_reply_body, reply_body)
1613
1614
    def test_smart_http_server_post_request_handler(self):
1615
        httpd = self.get_readonly_server()._get_httpd()
1616
1617
        socket = SampleSocket(
1618
            'POST /.bzr/smart %s \r\n' % self._protocol_version
1619
            # HTTP/1.1 posts must have a Content-Length (but it doesn't hurt
1620
            # for 1.0)
1621
            + 'Content-Length: 6\r\n'
1622
            '\r\n'
1623
            'hello\n')
1624
        # Beware: the ('localhost', 80) below is the
1625
        # client_address parameter, but we don't have one because
1626
        # we have defined a socket which is not bound to an
1627
        # address. The test framework never uses this client
1628
        # address, so far...
1629
        request_handler = http_utils.SmartRequestHandler(socket,
1630
                                                         ('localhost', 80),
1631
                                                         httpd)
1632
        response = socket.writefile.getvalue()
1633
        self.assertStartsWith(response, '%s 200 ' % self._protocol_version)
1634
        # This includes the end of the HTTP headers, and all the body.
1635
        expected_end_of_response = '\r\n\r\nok\x012\n'
1636
        self.assertEndsWith(response, expected_end_of_response)
1637
1638