/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Vincent Ladeuil
  • Date: 2008-01-03 08:44:12 UTC
  • mto: (3156.2.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 3158.
  • Revision ID: v.ladeuil+lp@free.fr-20080103084412-5bjr40bt0hbqorbk
Review feeback.

* bzrlib/tests/test_http.py:
(TestSpecificRequestHandler): Typo in doc string.

* bzrlib/tests/http_server.py:
(TestingHTTPServerMixin.tearDown): Mentioned by Aaron for win32.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Tests for HTTP implementations.
 
18
 
 
19
This module defines a load_tests() method that parametrize tests classes for
 
20
transport implementation, http protocol versions and authentication schemes.
 
21
"""
 
22
 
 
23
# TODO: Should be renamed to bzrlib.transport.http.tests?
 
24
# TODO: What about renaming to bzrlib.tests.transport.http ?
 
25
 
 
26
from cStringIO import StringIO
 
27
import httplib
 
28
import os
 
29
import select
 
30
import SimpleHTTPServer
 
31
import socket
 
32
import sys
 
33
import threading
 
34
 
 
35
import bzrlib
 
36
from bzrlib import (
 
37
    config,
 
38
    errors,
 
39
    osutils,
 
40
    tests,
 
41
    transport,
 
42
    ui,
 
43
    urlutils,
 
44
    )
 
45
from bzrlib.tests import (
 
46
    http_server,
 
47
    http_utils,
 
48
    )
 
49
from bzrlib.transport import (
 
50
    http,
 
51
    remote,
 
52
    )
 
53
from bzrlib.transport.http import (
 
54
    _urllib,
 
55
    _urllib2_wrappers,
 
56
    )
 
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()
 
126
    tp_classes= (SmartHTTPTunnellingTest,
 
127
                 TestDoCatchRedirections,
 
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
                   )
 
143
    is_also_testing_for_authentication = tests.condition_isinstance(
 
144
        tpa_classes)
 
145
 
 
146
    result = loader.suiteClass()
 
147
    for test_class in tests.iter_suite_tests(standard_tests):
 
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.
 
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))
 
157
        else:
 
158
            result.addTest(test_class)
 
159
    return result
 
160
 
 
161
 
 
162
class FakeManager(object):
 
163
 
 
164
    def __init__(self):
 
165
        self.credentials = []
 
166
 
 
167
    def add_password(self, realm, host, username, password):
 
168
        self.credentials.append([realm, host, username, password])
 
169
 
 
170
 
 
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
 
 
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
 
 
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
 
 
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()
 
265
        self.addCleanup(server.tearDown)
 
266
        self.assertIsInstance(server._httpd, http_server.TestingHTTPServer)
 
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()
 
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)
 
302
 
 
303
 
 
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:
 
312
            raise tests.TestSkipped('pycurl not present')
 
313
 
 
314
    _transport = property(_get_pycurl_maybe)
 
315
 
 
316
 
 
317
class TestHttpUrls(tests.TestCase):
 
318
 
 
319
    # TODO: This should be moved to authorization tests once they
 
320
    # are written.
 
321
 
 
322
    def test_url_parsing(self):
 
323
        f = FakeManager()
 
324
        url = http.extract_auth('http://example.com', f)
 
325
        self.assertEquals('http://example.com', url)
 
326
        self.assertEquals(0, len(f.credentials))
 
327
        url = http.extract_auth(
 
328
            'http://user:pass@www.bazaar-vcs.org/bzr/bzr.dev', f)
 
329
        self.assertEquals('http://www.bazaar-vcs.org/bzr/bzr.dev', url)
 
330
        self.assertEquals(1, len(f.credentials))
 
331
        self.assertEquals([None, 'www.bazaar-vcs.org', 'user', 'pass'],
 
332
                          f.credentials[0])
 
333
 
 
334
 
 
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
 
 
373
class TestHttps_pycurl(TestWithTransport_pycurl, tests.TestCase):
 
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:
 
388
            raise tests.TestSkipped('pycurl not present')
 
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
 
413
 
 
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):
 
484
        server = RecordingServer(expect_body_tail='end-of-body')
 
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
 
 
501
class TestRangeHeader(tests.TestCase):
 
502
    """Test range_header method"""
 
503
 
 
504
    def check_header(self, value, ranges=[], tail=0):
 
505
        offsets = [ (start, end - start + 1) for start, end in ranges]
 
506
        coalesce = transport.Transport._coalesce_offsets
 
507
        coalesced = list(coalesce(offsets, limit=0, fudge_factor=0))
 
508
        range_header = http.HttpTransportBase._range_header
 
509
        self.assertEqual(value, range_header(coalesced, tail))
 
510
 
 
511
    def test_range_header_single(self):
 
512
        self.check_header('0-9', ranges=[(0,9)])
 
513
        self.check_header('100-109', ranges=[(100,109)])
 
514
 
 
515
    def test_range_header_tail(self):
 
516
        self.check_header('-10', tail=10)
 
517
        self.check_header('-50', tail=50)
 
518
 
 
519
    def test_range_header_multi(self):
 
520
        self.check_header('0-9,100-200,300-5000',
 
521
                          ranges=[(0,9), (100, 200), (300,5000)])
 
522
 
 
523
    def test_range_header_mixed(self):
 
524
        self.check_header('0-9,300-5000,-50',
 
525
                          ranges=[(0,9), (300,5000)],
 
526
                          tail=50)
 
527
 
 
528
 
 
529
class TestSpecificRequestHandler(http_utils.TestCaseWithWebserver):
 
530
    """Tests a specific request handler.
 
531
 
 
532
    Daughter classes are expected to override _req_handler_class
 
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
 
 
542
    def _testing_pycurl(self):
 
543
        return pycurl_present and self._transport == PyCurlTransport
 
544
 
 
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)
 
583
        self.send_response(0, "Bad status")
 
584
        self.close_connection = 1
 
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):
 
605
    """Whatever request comes in, returns an invalid status"""
 
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
 
 
622
    def test_http_has(self):
 
623
        if self._testing_pycurl() and self._protocol_version == 'HTTP/1.1':
 
624
            raise tests.KnownFailure(
 
625
                'pycurl hangs if the server send back garbage')
 
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':
 
630
            raise tests.KnownFailure(
 
631
                'pycurl hangs if the server send back garbage')
 
632
        super(TestInvalidStatusServer, self).test_http_get()
 
633
 
 
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
 
 
697
class TestRecordingServer(tests.TestCase):
 
698
 
 
699
    def test_create(self):
 
700
        server = RecordingServer(expect_body_tail=None)
 
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):
 
706
        server = RecordingServer(expect_body_tail=None)
 
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):
 
717
        server = RecordingServer(expect_body_tail='c')
 
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',
 
724
                         osutils.recv_all(sock, 4096))
 
725
        self.assertEqual('abc', server.received_bytes)
 
726
 
 
727
 
 
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
 
789
        # single range will keep its size even if bigger than the limit.
 
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
 
 
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
 
 
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
 
 
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
 
 
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
 
 
936
    # Requests with more range specifiers will error out
 
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
 
 
973
class TestHttpProxyWhiteBox(tests.TestCase):
 
974
    """Whitebox test proxy http authorization.
 
975
 
 
976
    Only the urllib implementation is tested here.
 
977
    """
 
978
 
 
979
    def setUp(self):
 
980
        tests.TestCase.setUp(self)
 
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():
 
988
            self._old_env[name] = osutils.set_or_unset_env(name, value)
 
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):
 
995
        handler = _urllib2_wrappers.ProxyHandler()
 
996
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
 
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
 
 
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)
 
1009
 
 
1010
 
 
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
 
 
1134
    def create_transport_readonly_server(self):
 
1135
        return http_server.HttpServer(protocol_version=self._protocol_version)
 
1136
 
 
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)
 
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
            ]
 
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
 
 
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
 
 
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
 
 
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