/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/HTTPTestUtil.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-04-24 10:31:28 UTC
  • mfrom: (1684.1.2 bzr.mbp.integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060424103128-a637f56a7c529bad
(mbp) tutorial improvements

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
16
 
 
17
 
import base64
18
 
import re
19
 
try:
20
 
    from urllib.request import (
21
 
        parse_http_list,
22
 
        parse_keqv_list,
23
 
        )
24
 
except ImportError:  # python < 3
25
 
    from urllib2 import (
26
 
        parse_http_list,
27
 
        parse_keqv_list,
28
 
        )
29
 
 
30
 
 
31
 
from .. import (
32
 
    errors,
33
 
    osutils,
34
 
    tests,
35
 
    transport,
36
 
    )
37
 
from ..sixish import (
38
 
    BytesIO,
39
 
    )
40
 
from ..bzr.smart import (
41
 
    medium,
42
 
    )
43
 
from . import http_server
44
 
from ..transport import chroot
45
 
 
46
 
 
47
 
class HTTPServerWithSmarts(http_server.HttpServer):
48
 
    """HTTPServerWithSmarts extends the HttpServer with POST methods that will
49
 
    trigger a smart server to execute with a transport rooted at the rootdir of
50
 
    the HTTP server.
51
 
    """
52
 
 
53
 
    def __init__(self, protocol_version=None):
54
 
        http_server.HttpServer.__init__(self, SmartRequestHandler,
55
 
                                        protocol_version=protocol_version)
56
 
 
57
 
 
58
 
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
59
 
    """Extend TestingHTTPRequestHandler to support smart client POSTs.
60
 
 
61
 
    XXX: This duplicates a fair bit of the logic in breezy.transport.http.wsgi.
62
 
    """
63
 
 
64
 
    def do_POST(self):
65
 
        """Hand the request off to a smart server instance."""
66
 
        backing = transport.get_transport_from_path(
67
 
            self.server.test_case_server._home_dir)
68
 
        chroot_server = chroot.ChrootServer(backing)
69
 
        chroot_server.start_server()
70
 
        try:
71
 
            t = transport.get_transport_from_url(chroot_server.get_url())
72
 
            self.do_POST_inner(t)
73
 
        finally:
74
 
            chroot_server.stop_server()
75
 
 
76
 
    def do_POST_inner(self, chrooted_transport):
77
 
        self.send_response(200)
78
 
        self.send_header("Content-type", "application/octet-stream")
79
 
        if not self.path.endswith('.bzr/smart'):
80
 
            raise AssertionError(
81
 
                'POST to path not ending in .bzr/smart: %r' % (self.path,))
82
 
        t = chrooted_transport.clone(self.path[:-len('.bzr/smart')])
83
 
        # if this fails, we should return 400 bad request, but failure is
84
 
        # failure for now - RBC 20060919
85
 
        data_length = int(self.headers['Content-Length'])
86
 
        # TODO: We might like to support streaming responses.  1.0 allows no
87
 
        # Content-length in this case, so for integrity we should perform our
88
 
        # own chunking within the stream.
89
 
        # 1.1 allows chunked responses, and in this case we could chunk using
90
 
        # the HTTP chunking as this will allow HTTP persistence safely, even if
91
 
        # we have to stop early due to error, but we would also have to use the
92
 
        # HTTP trailer facility which may not be widely available.
93
 
        request_bytes = self.rfile.read(data_length)
94
 
        protocol_factory, unused_bytes = (
95
 
            medium._get_protocol_factory_for_bytes(request_bytes))
96
 
        out_buffer = BytesIO()
97
 
        smart_protocol_request = protocol_factory(t, out_buffer.write, '/')
98
 
        # Perhaps there should be a SmartServerHTTPMedium that takes care of
99
 
        # feeding the bytes in the http request to the smart_protocol_request,
100
 
        # but for now it's simpler to just feed the bytes directly.
101
 
        smart_protocol_request.accept_bytes(unused_bytes)
102
 
        if not (smart_protocol_request.next_read_size() == 0):
103
 
            raise errors.SmartProtocolError(
104
 
                "not finished reading, but all data sent to protocol.")
105
 
        self.send_header("Content-Length", str(len(out_buffer.getvalue())))
106
 
        self.end_headers()
107
 
        self.wfile.write(out_buffer.getvalue())
108
 
 
109
 
 
110
 
class TestCaseWithWebserver(tests.TestCaseWithTransport):
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import os
 
18
 
 
19
import bzrlib
 
20
from bzrlib.tests import TestCaseWithTransport
 
21
 
 
22
 
 
23
class TestCaseWithWebserver(TestCaseWithTransport):
111
24
    """A support class that provides readonly urls that are http://.
112
25
 
113
 
    This is done by forcing the readonly server to be an http
114
 
    one. This will currently fail if the primary transport is not
115
 
    backed by regular disk files.
 
26
    This is done by forcing the readonly server to be an http one. This 
 
27
    will current fail if the primary transport is not backed by regular disk
 
28
    files.
116
29
    """
117
30
 
118
 
    # These attributes can be overriden or parametrized by daughter clasess if
119
 
    # needed, but must exist so that the create_transport_readonly_server()
120
 
    # method (or any method creating an http(s) server) can propagate it.
121
 
    _protocol_version = None
122
 
    _url_protocol = 'http'
123
 
 
124
31
    def setUp(self):
125
32
        super(TestCaseWithWebserver, self).setUp()
126
 
        self.transport_readonly_server = http_server.HttpServer
127
 
 
128
 
    def create_transport_readonly_server(self):
129
 
        server = self.transport_readonly_server(
130
 
            protocol_version=self._protocol_version)
131
 
        server._url_protocol = self._url_protocol
132
 
        return server
133
 
 
134
 
 
135
 
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
136
 
    """A support class providing readonly urls on two servers that are http://.
137
 
 
138
 
    We set up two webservers to allows various tests involving
139
 
    proxies or redirections from one server to the other.
140
 
    """
141
 
 
142
 
    def setUp(self):
143
 
        super(TestCaseWithTwoWebservers, self).setUp()
144
 
        self.transport_secondary_server = http_server.HttpServer
145
 
        self.__secondary_server = None
146
 
 
147
 
    def create_transport_secondary_server(self):
148
 
        """Create a transport server from class defined at init.
149
 
 
150
 
        This is mostly a hook for daughter classes.
151
 
        """
152
 
        server = self.transport_secondary_server(
153
 
            protocol_version=self._protocol_version)
154
 
        server._url_protocol = self._url_protocol
155
 
        return server
156
 
 
157
 
    def get_secondary_server(self):
158
 
        """Get the server instance for the secondary transport."""
159
 
        if self.__secondary_server is None:
160
 
            self.__secondary_server = self.create_transport_secondary_server()
161
 
            self.start_server(self.__secondary_server)
162
 
        return self.__secondary_server
163
 
 
164
 
    def get_secondary_url(self, relpath=None):
165
 
        base = self.get_secondary_server().get_url()
166
 
        return self._adjust_url(base, relpath)
167
 
 
168
 
    def get_secondary_transport(self, relpath=None):
169
 
        t = transport.get_transport_from_url(self.get_secondary_url(relpath))
170
 
        self.assertTrue(t.is_readonly())
171
 
        return t
172
 
 
173
 
 
174
 
class ProxyServer(http_server.HttpServer):
175
 
    """A proxy test server for http transports."""
176
 
 
177
 
    proxy_requests = True
178
 
 
179
 
 
180
 
class RedirectRequestHandler(http_server.TestingHTTPRequestHandler):
181
 
    """Redirect all request to the specified server"""
182
 
 
183
 
    def parse_request(self):
184
 
        """Redirect a single HTTP request to another host"""
185
 
        valid = http_server.TestingHTTPRequestHandler.parse_request(self)
186
 
        if valid:
187
 
            tcs = self.server.test_case_server
188
 
            code, target = tcs.is_redirected(self.path)
189
 
            if code is not None and target is not None:
190
 
                # Redirect as instructed
191
 
                self.send_response(code)
192
 
                self.send_header('Location', target)
193
 
                # We do not send a body
194
 
                self.send_header('Content-Length', '0')
195
 
                self.end_headers()
196
 
                return False  # The job is done
197
 
            else:
198
 
                # We leave the parent class serve the request
199
 
                pass
200
 
        return valid
201
 
 
202
 
 
203
 
class HTTPServerRedirecting(http_server.HttpServer):
204
 
    """An HttpServer redirecting to another server """
205
 
 
206
 
    def __init__(self, request_handler=RedirectRequestHandler,
207
 
                 protocol_version=None):
208
 
        http_server.HttpServer.__init__(self, request_handler,
209
 
                                        protocol_version=protocol_version)
210
 
        # redirections is a list of tuples (source, target, code)
211
 
        # - source is a regexp for the paths requested
212
 
        # - target is a replacement for re.sub describing where
213
 
        #   the request will be redirected
214
 
        # - code is the http error code associated to the
215
 
        #   redirection (301 permanent, 302 temporarry, etc
216
 
        self.redirections = []
217
 
 
218
 
    def redirect_to(self, host, port):
219
 
        """Redirect all requests to a specific host:port"""
220
 
        self.redirections = [('(.*)',
221
 
                              r'http://%s:%s\1' % (host, port),
222
 
                              301)]
223
 
 
224
 
    def is_redirected(self, path):
225
 
        """Is the path redirected by this server.
226
 
 
227
 
        :param path: the requested relative path
228
 
 
229
 
        :returns: a tuple (code, target) if a matching
230
 
             redirection is found, (None, None) otherwise.
231
 
        """
232
 
        code = None
233
 
        target = None
234
 
        for (rsource, rtarget, rcode) in self.redirections:
235
 
            target, match = re.subn(rsource, rtarget, path, count=1)
236
 
            if match:
237
 
                code = rcode
238
 
                break  # The first match wins
239
 
            else:
240
 
                target = None
241
 
        return code, target
242
 
 
243
 
 
244
 
class TestCaseWithRedirectedWebserver(TestCaseWithTwoWebservers):
245
 
    """A support class providing redirections from one server to another.
246
 
 
247
 
    We set up two webservers to allows various tests involving
248
 
    redirections.
249
 
    The 'old' server is redirected to the 'new' server.
250
 
    """
251
 
 
252
 
    def setUp(self):
253
 
        super(TestCaseWithRedirectedWebserver, self).setUp()
254
 
        # The redirections will point to the new server
255
 
        self.new_server = self.get_readonly_server()
256
 
        # The requests to the old server will be redirected to the new server
257
 
        self.old_server = self.get_secondary_server()
258
 
 
259
 
    def create_transport_secondary_server(self):
260
 
        """Create the secondary server redirecting to the primary server"""
261
 
        new = self.get_readonly_server()
262
 
        redirecting = HTTPServerRedirecting(
263
 
            protocol_version=self._protocol_version)
264
 
        redirecting.redirect_to(new.host, new.port)
265
 
        redirecting._url_protocol = self._url_protocol
266
 
        return redirecting
267
 
 
268
 
    def get_old_url(self, relpath=None):
269
 
        base = self.old_server.get_url()
270
 
        return self._adjust_url(base, relpath)
271
 
 
272
 
    def get_old_transport(self, relpath=None):
273
 
        t = transport.get_transport_from_url(self.get_old_url(relpath))
274
 
        self.assertTrue(t.is_readonly())
275
 
        return t
276
 
 
277
 
    def get_new_url(self, relpath=None):
278
 
        base = self.new_server.get_url()
279
 
        return self._adjust_url(base, relpath)
280
 
 
281
 
    def get_new_transport(self, relpath=None):
282
 
        t = transport.get_transport_from_url(self.get_new_url(relpath))
283
 
        self.assertTrue(t.is_readonly())
284
 
        return t
285
 
 
286
 
 
287
 
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
288
 
    """Requires an authentication to process requests.
289
 
 
290
 
    This is intended to be used with a server that always and
291
 
    only use one authentication scheme (implemented by daughter
292
 
    classes).
293
 
    """
294
 
 
295
 
    # The following attributes should be defined in the server
296
 
    # - auth_header_sent: the header name sent to require auth
297
 
    # - auth_header_recv: the header received containing auth
298
 
    # - auth_error_code: the error code to indicate auth required
299
 
 
300
 
    def _require_authentication(self):
301
 
        # Note that we must update test_case_server *before*
302
 
        # sending the error or the client may try to read it
303
 
        # before we have sent the whole error back.
304
 
        tcs = self.server.test_case_server
305
 
        tcs.auth_required_errors += 1
306
 
        self.send_response(tcs.auth_error_code)
307
 
        self.send_header_auth_reqed()
308
 
        # We do not send a body
309
 
        self.send_header('Content-Length', '0')
310
 
        self.end_headers()
311
 
        return
312
 
 
313
 
    def do_GET(self):
314
 
        if self.authorized():
315
 
            return http_server.TestingHTTPRequestHandler.do_GET(self)
316
 
        else:
317
 
            return self._require_authentication()
318
 
 
319
 
    def do_HEAD(self):
320
 
        if self.authorized():
321
 
            return http_server.TestingHTTPRequestHandler.do_HEAD(self)
322
 
        else:
323
 
            return self._require_authentication()
324
 
 
325
 
 
326
 
class BasicAuthRequestHandler(AuthRequestHandler):
327
 
    """Implements the basic authentication of a request"""
328
 
 
329
 
    def authorized(self):
330
 
        tcs = self.server.test_case_server
331
 
        if tcs.auth_scheme != 'basic':
332
 
            return False
333
 
 
334
 
        auth_header = self.headers.get(tcs.auth_header_recv, None)
335
 
        if auth_header:
336
 
            scheme, raw_auth = auth_header.split(' ', 1)
337
 
            if scheme.lower() == tcs.auth_scheme:
338
 
                user, password = base64.b64decode(raw_auth).split(b':')
339
 
                return tcs.authorized(user.decode('ascii'),
340
 
                                      password.decode('ascii'))
341
 
 
342
 
        return False
343
 
 
344
 
    def send_header_auth_reqed(self):
345
 
        tcs = self.server.test_case_server
346
 
        self.send_header(tcs.auth_header_sent,
347
 
                         'Basic realm="%s"' % tcs.auth_realm)
348
 
 
349
 
 
350
 
# FIXME: We could send an Authentication-Info header too when
351
 
# the authentication is succesful
352
 
 
353
 
class DigestAuthRequestHandler(AuthRequestHandler):
354
 
    """Implements the digest authentication of a request.
355
 
 
356
 
    We need persistence for some attributes and that can't be
357
 
    achieved here since we get instantiated for each request. We
358
 
    rely on the DigestAuthServer to take care of them.
359
 
    """
360
 
 
361
 
    def authorized(self):
362
 
        tcs = self.server.test_case_server
363
 
 
364
 
        auth_header = self.headers.get(tcs.auth_header_recv, None)
365
 
        if auth_header is None:
366
 
            return False
367
 
        scheme, auth = auth_header.split(None, 1)
368
 
        if scheme.lower() == tcs.auth_scheme:
369
 
            auth_dict = parse_keqv_list(parse_http_list(auth))
370
 
 
371
 
            return tcs.digest_authorized(auth_dict, self.command)
372
 
 
373
 
        return False
374
 
 
375
 
    def send_header_auth_reqed(self):
376
 
        tcs = self.server.test_case_server
377
 
        header = 'Digest realm="%s", ' % tcs.auth_realm
378
 
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
379
 
                                                              'MD5')
380
 
        self.send_header(tcs.auth_header_sent, header)
381
 
 
382
 
 
383
 
class DigestAndBasicAuthRequestHandler(DigestAuthRequestHandler):
384
 
    """Implements a digest and basic authentication of a request.
385
 
 
386
 
    I.e. the server proposes both schemes and the client should choose the best
387
 
    one it can handle, which, in that case, should be digest, the only scheme
388
 
    accepted here.
389
 
    """
390
 
 
391
 
    def send_header_auth_reqed(self):
392
 
        tcs = self.server.test_case_server
393
 
        self.send_header(tcs.auth_header_sent,
394
 
                         'Basic realm="%s"' % tcs.auth_realm)
395
 
        header = 'Digest realm="%s", ' % tcs.auth_realm
396
 
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
397
 
                                                              'MD5')
398
 
        self.send_header(tcs.auth_header_sent, header)
399
 
 
400
 
 
401
 
class AuthServer(http_server.HttpServer):
402
 
    """Extends HttpServer with a dictionary of passwords.
403
 
 
404
 
    This is used as a base class for various schemes which should
405
 
    all use or redefined the associated AuthRequestHandler.
406
 
 
407
 
    Note that no users are defined by default, so add_user should
408
 
    be called before issuing the first request.
409
 
    """
410
 
 
411
 
    # The following attributes should be set dy daughter classes
412
 
    # and are used by AuthRequestHandler.
413
 
    auth_header_sent = None
414
 
    auth_header_recv = None
415
 
    auth_error_code = None
416
 
    auth_realm = u"Thou should not pass"
417
 
 
418
 
    def __init__(self, request_handler, auth_scheme,
419
 
                 protocol_version=None):
420
 
        http_server.HttpServer.__init__(self, request_handler,
421
 
                                        protocol_version=protocol_version)
422
 
        self.auth_scheme = auth_scheme
423
 
        self.password_of = {}
424
 
        self.auth_required_errors = 0
425
 
 
426
 
    def add_user(self, user, password):
427
 
        """Declare a user with an associated password.
428
 
 
429
 
        password can be empty, use an empty string ('') in that
430
 
        case, not None.
431
 
        """
432
 
        self.password_of[user] = password
433
 
 
434
 
    def authorized(self, user, password):
435
 
        """Check that the given user provided the right password"""
436
 
        expected_password = self.password_of.get(user, None)
437
 
        return expected_password is not None and password == expected_password
438
 
 
439
 
 
440
 
# FIXME: There is some code duplication with
441
 
# _urllib2_wrappers.py.DigestAuthHandler. If that duplication
442
 
# grows, it may require a refactoring. Also, we don't implement
443
 
# SHA algorithm nor MD5-sess here, but that does not seem worth
444
 
# it.
445
 
class DigestAuthServer(AuthServer):
446
 
    """A digest authentication server"""
447
 
 
448
 
    auth_nonce = 'now!'
449
 
 
450
 
    def __init__(self, request_handler, auth_scheme,
451
 
                 protocol_version=None):
452
 
        AuthServer.__init__(self, request_handler, auth_scheme,
453
 
                            protocol_version=protocol_version)
454
 
 
455
 
    def digest_authorized(self, auth, command):
456
 
        nonce = auth['nonce']
457
 
        if nonce != self.auth_nonce:
458
 
            return False
459
 
        realm = auth['realm']
460
 
        if realm != self.auth_realm:
461
 
            return False
462
 
        user = auth['username']
463
 
        if user not in self.password_of:
464
 
            return False
465
 
        algorithm = auth['algorithm']
466
 
        if algorithm != 'MD5':
467
 
            return False
468
 
        qop = auth['qop']
469
 
        if qop != 'auth':
470
 
            return False
471
 
 
472
 
        password = self.password_of[user]
473
 
 
474
 
        # Recalculate the response_digest to compare with the one
475
 
        # sent by the client
476
 
        A1 = ('%s:%s:%s' % (user, realm, password)).encode('utf-8')
477
 
        A2 = ('%s:%s' % (command, auth['uri'])).encode('utf-8')
478
 
 
479
 
        def H(x):
480
 
            return osutils.md5(x).hexdigest()
481
 
 
482
 
        def KD(secret, data):
483
 
            return H(("%s:%s" % (secret, data)).encode('utf-8'))
484
 
 
485
 
        nonce_count = int(auth['nc'], 16)
486
 
 
487
 
        ncvalue = '%08x' % nonce_count
488
 
 
489
 
        cnonce = auth['cnonce']
490
 
        noncebit = '%s:%s:%s:%s:%s' % (nonce, ncvalue, cnonce, qop, H(A2))
491
 
        response_digest = KD(H(A1), noncebit)
492
 
 
493
 
        return response_digest == auth['response']
494
 
 
495
 
 
496
 
class HTTPAuthServer(AuthServer):
497
 
    """An HTTP server requiring authentication"""
498
 
 
499
 
    def init_http_auth(self):
500
 
        self.auth_header_sent = 'WWW-Authenticate'
501
 
        self.auth_header_recv = 'Authorization'
502
 
        self.auth_error_code = 401
503
 
 
504
 
 
505
 
class ProxyAuthServer(AuthServer):
506
 
    """A proxy server requiring authentication"""
507
 
 
508
 
    def init_proxy_auth(self):
509
 
        self.proxy_requests = True
510
 
        self.auth_header_sent = 'Proxy-Authenticate'
511
 
        self.auth_header_recv = 'Proxy-Authorization'
512
 
        self.auth_error_code = 407
513
 
 
514
 
 
515
 
class HTTPBasicAuthServer(HTTPAuthServer):
516
 
    """An HTTP server requiring basic authentication"""
517
 
 
518
 
    def __init__(self, protocol_version=None):
519
 
        HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
520
 
                                protocol_version=protocol_version)
521
 
        self.init_http_auth()
522
 
 
523
 
 
524
 
class HTTPDigestAuthServer(DigestAuthServer, HTTPAuthServer):
525
 
    """An HTTP server requiring digest authentication"""
526
 
 
527
 
    def __init__(self, protocol_version=None):
528
 
        DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
529
 
                                  protocol_version=protocol_version)
530
 
        self.init_http_auth()
531
 
 
532
 
 
533
 
class HTTPBasicAndDigestAuthServer(DigestAuthServer, HTTPAuthServer):
534
 
    """An HTTP server requiring basic or digest authentication"""
535
 
 
536
 
    def __init__(self, protocol_version=None):
537
 
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
538
 
                                  'basicdigest',
539
 
                                  protocol_version=protocol_version)
540
 
        self.init_http_auth()
541
 
        # We really accept Digest only
542
 
        self.auth_scheme = 'digest'
543
 
 
544
 
 
545
 
class ProxyBasicAuthServer(ProxyAuthServer):
546
 
    """A proxy server requiring basic authentication"""
547
 
 
548
 
    def __init__(self, protocol_version=None):
549
 
        ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
550
 
                                 protocol_version=protocol_version)
551
 
        self.init_proxy_auth()
552
 
 
553
 
 
554
 
class ProxyDigestAuthServer(DigestAuthServer, ProxyAuthServer):
555
 
    """A proxy server requiring basic authentication"""
556
 
 
557
 
    def __init__(self, protocol_version=None):
558
 
        ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
559
 
                                 protocol_version=protocol_version)
560
 
        self.init_proxy_auth()
561
 
 
562
 
 
563
 
class ProxyBasicAndDigestAuthServer(DigestAuthServer, ProxyAuthServer):
564
 
    """An proxy server requiring basic or digest authentication"""
565
 
 
566
 
    def __init__(self, protocol_version=None):
567
 
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
568
 
                                  'basicdigest',
569
 
                                  protocol_version=protocol_version)
570
 
        self.init_proxy_auth()
571
 
        # We really accept Digest only
572
 
        self.auth_scheme = 'digest'
 
33
        self.transport_readonly_server = bzrlib.transport.http.HttpServer