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