~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/http_utils.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-03-13 23:45:11 UTC
  • mfrom: (3272.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080313234511-fkj5oa8gm3nrfcro
(Neil Martinsen-Burrell) Explain version-info --custom in the User
        Guide

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
from cStringIO import StringIO
 
18
import errno
 
19
import md5
18
20
import re
 
21
import sha
 
22
import socket
 
23
import threading
 
24
import time
19
25
import urllib2
 
26
import urlparse
20
27
 
21
28
 
22
29
from bzrlib import (
23
 
    errors,
24
 
    osutils,
25
30
    tests,
26
31
    transport,
27
32
    )
28
 
from bzrlib.smart import (
29
 
    medium,
30
 
    )
 
33
from bzrlib.smart import protocol
31
34
from bzrlib.tests import http_server
32
 
from bzrlib.transport import chroot
33
35
 
34
36
 
35
37
class HTTPServerWithSmarts(http_server.HttpServer):
44
46
 
45
47
 
46
48
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
47
 
    """Extend TestingHTTPRequestHandler to support smart client POSTs.
48
 
 
49
 
    XXX: This duplicates a fair bit of the logic in bzrlib.transport.http.wsgi.
50
 
    """
 
49
    """Extend TestingHTTPRequestHandler to support smart client POSTs."""
51
50
 
52
51
    def do_POST(self):
53
52
        """Hand the request off to a smart server instance."""
54
 
        backing = transport.get_transport_from_path(
55
 
            self.server.test_case_server._home_dir)
56
 
        chroot_server = chroot.ChrootServer(backing)
57
 
        chroot_server.start_server()
58
 
        try:
59
 
            t = transport.get_transport_from_url(chroot_server.get_url())
60
 
            self.do_POST_inner(t)
61
 
        finally:
62
 
            chroot_server.stop_server()
63
 
 
64
 
    def do_POST_inner(self, chrooted_transport):
65
53
        self.send_response(200)
66
54
        self.send_header("Content-type", "application/octet-stream")
67
 
        if not self.path.endswith('.bzr/smart'):
68
 
            raise AssertionError(
69
 
                'POST to path not ending in .bzr/smart: %r' % (self.path,))
70
 
        t = chrooted_transport.clone(self.path[:-len('.bzr/smart')])
71
 
        # if this fails, we should return 400 bad request, but failure is
72
 
        # failure for now - RBC 20060919
73
 
        data_length = int(self.headers['Content-Length'])
 
55
        t = transport.get_transport(self.server.test_case_server._home_dir)
74
56
        # TODO: We might like to support streaming responses.  1.0 allows no
75
57
        # Content-length in this case, so for integrity we should perform our
76
58
        # own chunking within the stream.
78
60
        # the HTTP chunking as this will allow HTTP persistence safely, even if
79
61
        # we have to stop early due to error, but we would also have to use the
80
62
        # HTTP trailer facility which may not be widely available.
81
 
        request_bytes = self.rfile.read(data_length)
82
 
        protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
83
 
            request_bytes)
84
63
        out_buffer = StringIO()
85
 
        smart_protocol_request = protocol_factory(t, out_buffer.write, '/')
 
64
        smart_protocol_request = protocol.SmartServerRequestProtocolOne(
 
65
                t, out_buffer.write)
 
66
        # if this fails, we should return 400 bad request, but failure is
 
67
        # failure for now - RBC 20060919
 
68
        data_length = int(self.headers['Content-Length'])
86
69
        # Perhaps there should be a SmartServerHTTPMedium that takes care of
87
70
        # feeding the bytes in the http request to the smart_protocol_request,
88
71
        # but for now it's simpler to just feed the bytes directly.
89
 
        smart_protocol_request.accept_bytes(unused_bytes)
90
 
        if not (smart_protocol_request.next_read_size() == 0):
91
 
            raise errors.SmartProtocolError(
92
 
                "not finished reading, but all data sent to protocol.")
 
72
        smart_protocol_request.accept_bytes(self.rfile.read(data_length))
 
73
        assert smart_protocol_request.next_read_size() == 0, (
 
74
            "not finished reading, but all data sent to protocol.")
93
75
        self.send_header("Content-Length", str(len(out_buffer.getvalue())))
94
76
        self.end_headers()
95
77
        self.wfile.write(out_buffer.getvalue())
102
84
    one. This will currently fail if the primary transport is not
103
85
    backed by regular disk files.
104
86
    """
105
 
 
106
 
    # These attributes can be overriden or parametrized by daughter clasess if
107
 
    # needed, but must exist so that the create_transport_readonly_server()
108
 
    # method (or any method creating an http(s) server) can propagate it.
109
 
    _protocol_version = None
110
 
    _url_protocol = 'http'
111
 
 
112
87
    def setUp(self):
113
88
        super(TestCaseWithWebserver, self).setUp()
114
89
        self.transport_readonly_server = http_server.HttpServer
115
90
 
116
 
    def create_transport_readonly_server(self):
117
 
        server = self.transport_readonly_server(
118
 
            protocol_version=self._protocol_version)
119
 
        server._url_protocol = self._url_protocol
120
 
        return server
121
 
 
122
91
 
123
92
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
124
93
    """A support class providing readonly urls on two servers that are http://.
136
105
 
137
106
        This is mostly a hook for daughter classes.
138
107
        """
139
 
        server = self.transport_secondary_server(
140
 
            protocol_version=self._protocol_version)
141
 
        server._url_protocol = self._url_protocol
142
 
        return server
 
108
        return self.transport_secondary_server()
143
109
 
144
110
    def get_secondary_server(self):
145
111
        """Get the server instance for the secondary transport."""
146
112
        if self.__secondary_server is None:
147
113
            self.__secondary_server = self.create_transport_secondary_server()
148
 
            self.start_server(self.__secondary_server)
 
114
            self.__secondary_server.setUp()
 
115
            self.addCleanup(self.__secondary_server.tearDown)
149
116
        return self.__secondary_server
150
117
 
151
 
    def get_secondary_url(self, relpath=None):
152
 
        base = self.get_secondary_server().get_url()
153
 
        return self._adjust_url(base, relpath)
154
 
 
155
 
    def get_secondary_transport(self, relpath=None):
156
 
        t = transport.get_transport_from_url(self.get_secondary_url(relpath))
157
 
        self.assertTrue(t.is_readonly())
158
 
        return t
159
 
 
160
118
 
161
119
class ProxyServer(http_server.HttpServer):
162
120
    """A proxy test server for http transports."""
236
194
   The 'old' server is redirected to the 'new' server.
237
195
   """
238
196
 
 
197
   def create_transport_secondary_server(self):
 
198
       """Create the secondary server redirecting to the primary server"""
 
199
       new = self.get_readonly_server()
 
200
       redirecting = HTTPServerRedirecting()
 
201
       redirecting.redirect_to(new.host, new.port)
 
202
       return redirecting
 
203
 
239
204
   def setUp(self):
240
205
       super(TestCaseWithRedirectedWebserver, self).setUp()
241
206
       # The redirections will point to the new server
242
207
       self.new_server = self.get_readonly_server()
243
 
       # The requests to the old server will be redirected to the new server
 
208
       # The requests to the old server will be redirected
244
209
       self.old_server = self.get_secondary_server()
245
210
 
246
 
   def create_transport_secondary_server(self):
247
 
       """Create the secondary server redirecting to the primary server"""
248
 
       new = self.get_readonly_server()
249
 
       redirecting = HTTPServerRedirecting(
250
 
           protocol_version=self._protocol_version)
251
 
       redirecting.redirect_to(new.host, new.port)
252
 
       redirecting._url_protocol = self._url_protocol
253
 
       return redirecting
254
 
 
255
 
   def get_old_url(self, relpath=None):
256
 
        base = self.old_server.get_url()
257
 
        return self._adjust_url(base, relpath)
258
 
 
259
 
   def get_old_transport(self, relpath=None):
260
 
        t = transport.get_transport_from_url(self.get_old_url(relpath))
261
 
        self.assertTrue(t.is_readonly())
262
 
        return t
263
 
 
264
 
   def get_new_url(self, relpath=None):
265
 
        base = self.new_server.get_url()
266
 
        return self._adjust_url(base, relpath)
267
 
 
268
 
   def get_new_transport(self, relpath=None):
269
 
        t = transport.get_transport_from_url(self.get_new_url(relpath))
270
 
        self.assertTrue(t.is_readonly())
271
 
        return t
272
 
 
273
211
 
274
212
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
275
213
    """Requires an authentication to process requests.
284
222
    # - auth_header_recv: the header received containing auth
285
223
    # - auth_error_code: the error code to indicate auth required
286
224
 
287
 
    def _require_authentication(self):
288
 
        # Note that we must update test_case_server *before*
289
 
        # sending the error or the client may try to read it
290
 
        # before we have sent the whole error back.
291
 
        tcs = self.server.test_case_server
292
 
        tcs.auth_required_errors += 1
293
 
        self.send_response(tcs.auth_error_code)
294
 
        self.send_header_auth_reqed()
295
 
        # We do not send a body
296
 
        self.send_header('Content-Length', '0')
297
 
        self.end_headers()
298
 
        return
299
 
 
300
225
    def do_GET(self):
301
226
        if self.authorized():
302
227
            return http_server.TestingHTTPRequestHandler.do_GET(self)
303
228
        else:
304
 
            return self._require_authentication()
305
 
 
306
 
    def do_HEAD(self):
307
 
        if self.authorized():
308
 
            return http_server.TestingHTTPRequestHandler.do_HEAD(self)
309
 
        else:
310
 
            return self._require_authentication()
 
229
            # Note that we must update test_case_server *before*
 
230
            # sending the error or the client may try to read it
 
231
            # before we have sent the whole error back.
 
232
            tcs = self.server.test_case_server
 
233
            tcs.auth_required_errors += 1
 
234
            self.send_response(tcs.auth_error_code)
 
235
            self.send_header_auth_reqed()
 
236
            # We do not send a body
 
237
            self.send_header('Content-Length', '0')
 
238
            self.end_headers()
 
239
            return
311
240
 
312
241
 
313
242
class BasicAuthRequestHandler(AuthRequestHandler):
346
275
 
347
276
    def authorized(self):
348
277
        tcs = self.server.test_case_server
 
278
        if tcs.auth_scheme != 'digest':
 
279
            return False
349
280
 
350
281
        auth_header = self.headers.get(tcs.auth_header_recv, None)
351
282
        if auth_header is None:
366
297
        self.send_header(tcs.auth_header_sent,header)
367
298
 
368
299
 
369
 
class DigestAndBasicAuthRequestHandler(DigestAuthRequestHandler):
370
 
    """Implements a digest and basic authentication of a request.
371
 
 
372
 
    I.e. the server proposes both schemes and the client should choose the best
373
 
    one it can handle, which, in that case, should be digest, the only scheme
374
 
    accepted here.
375
 
    """
376
 
 
377
 
    def send_header_auth_reqed(self):
378
 
        tcs = self.server.test_case_server
379
 
        self.send_header(tcs.auth_header_sent,
380
 
                         'Basic realm="%s"' % tcs.auth_realm)
381
 
        header = 'Digest realm="%s", ' % tcs.auth_realm
382
 
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
383
 
                                                              'MD5')
384
 
        self.send_header(tcs.auth_header_sent,header)
385
 
 
386
 
 
387
300
class AuthServer(http_server.HttpServer):
388
301
    """Extends HttpServer with a dictionary of passwords.
389
302
 
462
375
        A1 = '%s:%s:%s' % (user, realm, password)
463
376
        A2 = '%s:%s' % (command, auth['uri'])
464
377
 
465
 
        H = lambda x: osutils.md5(x).hexdigest()
 
378
        H = lambda x: md5.new(x).hexdigest()
466
379
        KD = lambda secret, data: H("%s:%s" % (secret, data))
467
380
 
468
381
        nonce_count = int(auth['nc'], 16)
475
388
 
476
389
        return response_digest == auth['response']
477
390
 
478
 
 
479
391
class HTTPAuthServer(AuthServer):
480
392
    """An HTTP server requiring authentication"""
481
393
 
513
425
        self.init_http_auth()
514
426
 
515
427
 
516
 
class HTTPBasicAndDigestAuthServer(DigestAuthServer, HTTPAuthServer):
517
 
    """An HTTP server requiring basic or digest authentication"""
518
 
 
519
 
    def __init__(self, protocol_version=None):
520
 
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
521
 
                                  'basicdigest',
522
 
                                  protocol_version=protocol_version)
523
 
        self.init_http_auth()
524
 
        # We really accept Digest only
525
 
        self.auth_scheme = 'digest'
526
 
 
527
 
 
528
428
class ProxyBasicAuthServer(ProxyAuthServer):
529
429
    """A proxy server requiring basic authentication"""
530
430
 
543
443
        self.init_proxy_auth()
544
444
 
545
445
 
546
 
class ProxyBasicAndDigestAuthServer(DigestAuthServer, ProxyAuthServer):
547
 
    """An proxy server requiring basic or digest authentication"""
548
 
 
549
 
    def __init__(self, protocol_version=None):
550
 
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
551
 
                                  'basicdigest',
552
 
                                  protocol_version=protocol_version)
553
 
        self.init_proxy_auth()
554
 
        # We really accept Digest only
555
 
        self.auth_scheme = 'digest'
556
 
 
557