~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/http_utils.py

  • Committer: Vincent Ladeuil
  • Date: 2008-09-11 19:36:38 UTC
  • mfrom: (3703 +trunk)
  • mto: (3705.1.1 trunk2)
  • mto: This revision was merged to the branch mainline in revision 3708.
  • Revision ID: v.ladeuil+lp@free.fr-20080911193638-wtjyc1kcmacc6t1f
merge bzr.dev

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
20
 
 
 
26
import urlparse
21
27
 
22
28
from bzrlib import (
23
29
    errors,
24
 
    osutils,
25
30
    tests,
26
 
    transport,
27
 
    )
28
 
from bzrlib.smart import (
29
 
    medium,
30
 
    )
 
31
    )
 
32
from bzrlib.smart import medium, protocol
31
33
from bzrlib.tests import http_server
32
 
from bzrlib.transport import chroot
 
34
from bzrlib.transport import (
 
35
    chroot,
 
36
    get_transport,
 
37
    )
33
38
 
34
39
 
35
40
class HTTPServerWithSmarts(http_server.HttpServer):
45
50
 
46
51
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
47
52
    """Extend TestingHTTPRequestHandler to support smart client POSTs.
48
 
 
 
53
    
49
54
    XXX: This duplicates a fair bit of the logic in bzrlib.transport.http.wsgi.
50
55
    """
51
56
 
52
57
    def do_POST(self):
53
58
        """Hand the request off to a smart server instance."""
54
 
        backing = transport.get_transport_from_path(
55
 
            self.server.test_case_server._home_dir)
 
59
        backing = get_transport(self.server.test_case_server._home_dir)
56
60
        chroot_server = chroot.ChrootServer(backing)
57
 
        chroot_server.start_server()
 
61
        chroot_server.setUp()
58
62
        try:
59
 
            t = transport.get_transport_from_url(chroot_server.get_url())
 
63
            t = get_transport(chroot_server.get_url())
60
64
            self.do_POST_inner(t)
61
65
        finally:
62
 
            chroot_server.stop_server()
 
66
            chroot_server.tearDown()
63
67
 
64
68
    def do_POST_inner(self, chrooted_transport):
65
69
        self.send_response(200)
102
106
    one. This will currently fail if the primary transport is not
103
107
    backed by regular disk files.
104
108
    """
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
109
    def setUp(self):
113
110
        super(TestCaseWithWebserver, self).setUp()
114
111
        self.transport_readonly_server = http_server.HttpServer
115
112
 
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
113
 
123
114
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
124
115
    """A support class providing readonly urls on two servers that are http://.
136
127
 
137
128
        This is mostly a hook for daughter classes.
138
129
        """
139
 
        server = self.transport_secondary_server(
140
 
            protocol_version=self._protocol_version)
141
 
        server._url_protocol = self._url_protocol
142
 
        return server
 
130
        return self.transport_secondary_server()
143
131
 
144
132
    def get_secondary_server(self):
145
133
        """Get the server instance for the secondary transport."""
146
134
        if self.__secondary_server is None:
147
135
            self.__secondary_server = self.create_transport_secondary_server()
148
 
            self.start_server(self.__secondary_server)
 
136
            self.__secondary_server.setUp()
 
137
            self.addCleanup(self.__secondary_server.tearDown)
149
138
        return self.__secondary_server
150
139
 
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
140
 
161
141
class ProxyServer(http_server.HttpServer):
162
142
    """A proxy test server for http transports."""
236
216
   The 'old' server is redirected to the 'new' server.
237
217
   """
238
218
 
 
219
   def create_transport_secondary_server(self):
 
220
       """Create the secondary server redirecting to the primary server"""
 
221
       new = self.get_readonly_server()
 
222
       redirecting = HTTPServerRedirecting()
 
223
       redirecting.redirect_to(new.host, new.port)
 
224
       return redirecting
 
225
 
239
226
   def setUp(self):
240
227
       super(TestCaseWithRedirectedWebserver, self).setUp()
241
228
       # The redirections will point to the new server
242
229
       self.new_server = self.get_readonly_server()
243
 
       # The requests to the old server will be redirected to the new server
 
230
       # The requests to the old server will be redirected
244
231
       self.old_server = self.get_secondary_server()
245
232
 
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
233
 
274
234
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
275
235
    """Requires an authentication to process requests.
284
244
    # - auth_header_recv: the header received containing auth
285
245
    # - auth_error_code: the error code to indicate auth required
286
246
 
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
247
    def do_GET(self):
301
248
        if self.authorized():
302
249
            return http_server.TestingHTTPRequestHandler.do_GET(self)
303
250
        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()
 
251
            # Note that we must update test_case_server *before*
 
252
            # sending the error or the client may try to read it
 
253
            # before we have sent the whole error back.
 
254
            tcs = self.server.test_case_server
 
255
            tcs.auth_required_errors += 1
 
256
            self.send_response(tcs.auth_error_code)
 
257
            self.send_header_auth_reqed()
 
258
            # We do not send a body
 
259
            self.send_header('Content-Length', '0')
 
260
            self.end_headers()
 
261
            return
311
262
 
312
263
 
313
264
class BasicAuthRequestHandler(AuthRequestHandler):
346
297
 
347
298
    def authorized(self):
348
299
        tcs = self.server.test_case_server
 
300
        if tcs.auth_scheme != 'digest':
 
301
            return False
349
302
 
350
303
        auth_header = self.headers.get(tcs.auth_header_recv, None)
351
304
        if auth_header is None:
366
319
        self.send_header(tcs.auth_header_sent,header)
367
320
 
368
321
 
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
322
class AuthServer(http_server.HttpServer):
388
323
    """Extends HttpServer with a dictionary of passwords.
389
324
 
462
397
        A1 = '%s:%s:%s' % (user, realm, password)
463
398
        A2 = '%s:%s' % (command, auth['uri'])
464
399
 
465
 
        H = lambda x: osutils.md5(x).hexdigest()
 
400
        H = lambda x: md5.new(x).hexdigest()
466
401
        KD = lambda secret, data: H("%s:%s" % (secret, data))
467
402
 
468
403
        nonce_count = int(auth['nc'], 16)
475
410
 
476
411
        return response_digest == auth['response']
477
412
 
478
 
 
479
413
class HTTPAuthServer(AuthServer):
480
414
    """An HTTP server requiring authentication"""
481
415
 
513
447
        self.init_http_auth()
514
448
 
515
449
 
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
450
class ProxyBasicAuthServer(ProxyAuthServer):
529
451
    """A proxy server requiring basic authentication"""
530
452
 
543
465
        self.init_proxy_auth()
544
466
 
545
467
 
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