~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/http_utils.py

  • Committer: Martin Pool
  • Date: 2010-01-29 10:36:23 UTC
  • mto: This revision was merged to the branch mainline in revision 4992.
  • Revision ID: mbp@sourcefrog.net-20100129103623-hywka5hymo5z13jw
Change url to canonical.com or wiki, plus some doc improvements in passing

Show diffs side-by-side

added added

removed removed

Lines of Context:
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
from cStringIO import StringIO
18
18
import errno
19
 
from SimpleHTTPServer import SimpleHTTPRequestHandler
20
19
import re
21
20
import socket
 
21
import threading
 
22
import time
 
23
import urllib2
22
24
import urlparse
23
25
 
24
 
from bzrlib.tests import TestCaseWithTransport
25
 
from bzrlib.tests.HttpServer import (
26
 
    HttpServer,
27
 
    TestingHTTPRequestHandler,
 
26
 
 
27
from bzrlib import (
 
28
    errors,
 
29
    osutils,
 
30
    tests,
28
31
    )
 
32
from bzrlib.smart import medium, protocol
 
33
from bzrlib.tests import http_server
29
34
from bzrlib.transport import (
 
35
    chroot,
30
36
    get_transport,
31
37
    )
32
 
from bzrlib.smart import protocol
33
 
 
34
 
 
35
 
class WallRequestHandler(TestingHTTPRequestHandler):
36
 
    """Whatever request comes in, close the connection"""
37
 
 
38
 
    def handle_one_request(self):
39
 
        """Handle a single HTTP request, by abruptly closing the connection"""
40
 
        self.close_connection = 1
41
 
 
42
 
 
43
 
class BadStatusRequestHandler(TestingHTTPRequestHandler):
44
 
    """Whatever request comes in, returns a bad status"""
45
 
 
46
 
    def parse_request(self):
47
 
        """Fakes handling a single HTTP request, returns a bad status"""
48
 
        ignored = TestingHTTPRequestHandler.parse_request(self)
49
 
        try:
50
 
            self.send_response(0, "Bad status")
51
 
            self.end_headers()
52
 
        except socket.error, e:
53
 
            # We don't want to pollute the test results with
54
 
            # spurious server errors while test succeed. In our
55
 
            # case, it may occur that the test has already read
56
 
            # the 'Bad Status' and closed the socket while we are
57
 
            # still trying to send some headers... So the test is
58
 
            # ok, but if we raise the exception, the output is
59
 
            # dirty. So we don't raise, but we close the
60
 
            # connection, just to be safe :)
61
 
            spurious = [errno.EPIPE,
62
 
                        errno.ECONNRESET,
63
 
                        errno.ECONNABORTED,
64
 
                        ]
65
 
            if (len(e.args) > 0) and (e.args[0] in spurious):
66
 
                self.close_connection = 1
67
 
                pass
68
 
            else:
69
 
                raise
70
 
        return False
71
 
 
72
 
 
73
 
class InvalidStatusRequestHandler(TestingHTTPRequestHandler):
74
 
    """Whatever request comes in, returns am invalid status"""
75
 
 
76
 
    def parse_request(self):
77
 
        """Fakes handling a single HTTP request, returns a bad status"""
78
 
        ignored = TestingHTTPRequestHandler.parse_request(self)
79
 
        self.wfile.write("Invalid status line\r\n")
80
 
        return False
81
 
 
82
 
 
83
 
class BadProtocolRequestHandler(TestingHTTPRequestHandler):
84
 
    """Whatever request comes in, returns a bad protocol version"""
85
 
 
86
 
    def parse_request(self):
87
 
        """Fakes handling a single HTTP request, returns a bad status"""
88
 
        ignored = TestingHTTPRequestHandler.parse_request(self)
89
 
        # Returns an invalid protocol version, but curl just
90
 
        # ignores it and those cannot be tested.
91
 
        self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
92
 
                                           404,
93
 
                                           'Look at my protocol version'))
94
 
        return False
95
 
 
96
 
 
97
 
class ForbiddenRequestHandler(TestingHTTPRequestHandler):
98
 
    """Whatever request comes in, returns a 403 code"""
99
 
 
100
 
    def parse_request(self):
101
 
        """Handle a single HTTP request, by replying we cannot handle it"""
102
 
        ignored = TestingHTTPRequestHandler.parse_request(self)
103
 
        self.send_error(403)
104
 
        return False
105
 
 
106
 
 
107
 
class HTTPServerWithSmarts(HttpServer):
 
38
 
 
39
 
 
40
class HTTPServerWithSmarts(http_server.HttpServer):
108
41
    """HTTPServerWithSmarts extends the HttpServer with POST methods that will
109
42
    trigger a smart server to execute with a transport rooted at the rootdir of
110
43
    the HTTP server.
111
44
    """
112
45
 
113
 
    def __init__(self):
114
 
        HttpServer.__init__(self, SmartRequestHandler)
115
 
 
116
 
 
117
 
class SmartRequestHandler(TestingHTTPRequestHandler):
118
 
    """Extend TestingHTTPRequestHandler to support smart client POSTs."""
 
46
    def __init__(self, protocol_version=None):
 
47
        http_server.HttpServer.__init__(self, SmartRequestHandler,
 
48
                                        protocol_version=protocol_version)
 
49
 
 
50
 
 
51
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
 
52
    """Extend TestingHTTPRequestHandler to support smart client POSTs.
 
53
 
 
54
    XXX: This duplicates a fair bit of the logic in bzrlib.transport.http.wsgi.
 
55
    """
119
56
 
120
57
    def do_POST(self):
121
58
        """Hand the request off to a smart server instance."""
 
59
        backing = get_transport(self.server.test_case_server._home_dir)
 
60
        chroot_server = chroot.ChrootServer(backing)
 
61
        chroot_server.start_server()
 
62
        try:
 
63
            t = get_transport(chroot_server.get_url())
 
64
            self.do_POST_inner(t)
 
65
        finally:
 
66
            chroot_server.stop_server()
 
67
 
 
68
    def do_POST_inner(self, chrooted_transport):
122
69
        self.send_response(200)
123
70
        self.send_header("Content-type", "application/octet-stream")
124
 
        transport = get_transport(self.server.test_case_server._home_dir)
 
71
        if not self.path.endswith('.bzr/smart'):
 
72
            raise AssertionError(
 
73
                'POST to path not ending in .bzr/smart: %r' % (self.path,))
 
74
        t = chrooted_transport.clone(self.path[:-len('.bzr/smart')])
 
75
        # if this fails, we should return 400 bad request, but failure is
 
76
        # failure for now - RBC 20060919
 
77
        data_length = int(self.headers['Content-Length'])
125
78
        # TODO: We might like to support streaming responses.  1.0 allows no
126
79
        # Content-length in this case, so for integrity we should perform our
127
80
        # own chunking within the stream.
129
82
        # the HTTP chunking as this will allow HTTP persistence safely, even if
130
83
        # we have to stop early due to error, but we would also have to use the
131
84
        # HTTP trailer facility which may not be widely available.
 
85
        request_bytes = self.rfile.read(data_length)
 
86
        protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
 
87
            request_bytes)
132
88
        out_buffer = StringIO()
133
 
        smart_protocol_request = protocol.SmartServerRequestProtocolOne(
134
 
                transport, out_buffer.write)
135
 
        # if this fails, we should return 400 bad request, but failure is
136
 
        # failure for now - RBC 20060919
137
 
        data_length = int(self.headers['Content-Length'])
 
89
        smart_protocol_request = protocol_factory(t, out_buffer.write, '/')
138
90
        # Perhaps there should be a SmartServerHTTPMedium that takes care of
139
91
        # feeding the bytes in the http request to the smart_protocol_request,
140
92
        # but for now it's simpler to just feed the bytes directly.
141
 
        smart_protocol_request.accept_bytes(self.rfile.read(data_length))
142
 
        assert smart_protocol_request.next_read_size() == 0, (
143
 
            "not finished reading, but all data sent to protocol.")
 
93
        smart_protocol_request.accept_bytes(unused_bytes)
 
94
        if not (smart_protocol_request.next_read_size() == 0):
 
95
            raise errors.SmartProtocolError(
 
96
                "not finished reading, but all data sent to protocol.")
144
97
        self.send_header("Content-Length", str(len(out_buffer.getvalue())))
145
98
        self.end_headers()
146
99
        self.wfile.write(out_buffer.getvalue())
147
100
 
148
101
 
149
 
class SingleRangeRequestHandler(TestingHTTPRequestHandler):
150
 
    """Always reply to range request as if they were single.
151
 
 
152
 
    Don't be explicit about it, just to annoy the clients.
153
 
    """
154
 
 
155
 
    def get_multiple_ranges(self, file, file_size, ranges):
156
 
        """Answer as if it was a single range request and ignores the rest"""
157
 
        (start, end) = ranges[0]
158
 
        return self.get_single_range(file, file_size, start, end)
159
 
 
160
 
 
161
 
class NoRangeRequestHandler(TestingHTTPRequestHandler):
162
 
    """Ignore range requests without notice"""
163
 
 
164
 
    # Just bypass the range handling done by TestingHTTPRequestHandler
165
 
    do_GET = SimpleHTTPRequestHandler.do_GET
166
 
 
167
 
 
168
 
class TestCaseWithWebserver(TestCaseWithTransport):
 
102
class TestCaseWithWebserver(tests.TestCaseWithTransport):
169
103
    """A support class that provides readonly urls that are http://.
170
104
 
171
105
    This is done by forcing the readonly server to be an http
174
108
    """
175
109
    def setUp(self):
176
110
        super(TestCaseWithWebserver, self).setUp()
177
 
        self.transport_readonly_server = HttpServer
 
111
        self.transport_readonly_server = http_server.HttpServer
178
112
 
179
113
 
180
114
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
185
119
    """
186
120
    def setUp(self):
187
121
        super(TestCaseWithTwoWebservers, self).setUp()
188
 
        self.transport_secondary_server = HttpServer
 
122
        self.transport_secondary_server = http_server.HttpServer
189
123
        self.__secondary_server = None
190
124
 
191
125
    def create_transport_secondary_server(self):
199
133
        """Get the server instance for the secondary transport."""
200
134
        if self.__secondary_server is None:
201
135
            self.__secondary_server = self.create_transport_secondary_server()
202
 
            self.__secondary_server.setUp()
203
 
            self.addCleanup(self.__secondary_server.tearDown)
 
136
            self.start_server(self.__secondary_server)
204
137
        return self.__secondary_server
205
138
 
206
139
 
207
 
class FakeProxyRequestHandler(TestingHTTPRequestHandler):
208
 
    """Append a '-proxied' suffix to file served"""
209
 
 
210
 
    def translate_path(self, path):
211
 
        # We need to act as a proxy and accept absolute urls,
212
 
        # which SimpleHTTPRequestHandler (grand parent) is not
213
 
        # ready for. So we just drop the protocol://host:port
214
 
        # part in front of the request-url (because we know we
215
 
        # would not forward the request to *another* proxy).
216
 
 
217
 
        # So we do what SimpleHTTPRequestHandler.translate_path
218
 
        # do beginning with python 2.4.3: abandon query
219
 
        # parameters, scheme, host port, etc (which ensure we
220
 
        # provide the right behaviour on all python versions).
221
 
        path = urlparse.urlparse(path)[2]
222
 
        # And now, we can apply *our* trick to proxy files
223
 
        self.path += '-proxied'
224
 
        # An finally we leave our mother class do whatever it
225
 
        # wants with the path
226
 
        return TestingHTTPRequestHandler.translate_path(self, path)
227
 
 
228
 
 
229
 
class RedirectRequestHandler(TestingHTTPRequestHandler):
 
140
class ProxyServer(http_server.HttpServer):
 
141
    """A proxy test server for http transports."""
 
142
 
 
143
    proxy_requests = True
 
144
 
 
145
 
 
146
class RedirectRequestHandler(http_server.TestingHTTPRequestHandler):
230
147
    """Redirect all request to the specified server"""
231
148
 
232
149
    def parse_request(self):
233
150
        """Redirect a single HTTP request to another host"""
234
 
        valid = TestingHTTPRequestHandler.parse_request(self)
 
151
        valid = http_server.TestingHTTPRequestHandler.parse_request(self)
235
152
        if valid:
236
153
            tcs = self.server.test_case_server
237
154
            code, target = tcs.is_redirected(self.path)
239
156
                # Redirect as instructed
240
157
                self.send_response(code)
241
158
                self.send_header('Location', target)
 
159
                # We do not send a body
 
160
                self.send_header('Content-Length', '0')
242
161
                self.end_headers()
243
162
                return False # The job is done
244
163
            else:
247
166
        return valid
248
167
 
249
168
 
250
 
class HTTPServerRedirecting(HttpServer):
 
169
class HTTPServerRedirecting(http_server.HttpServer):
251
170
    """An HttpServer redirecting to another server """
252
171
 
253
 
    def __init__(self, request_handler=RedirectRequestHandler):
254
 
        HttpServer.__init__(self, request_handler)
 
172
    def __init__(self, request_handler=RedirectRequestHandler,
 
173
                 protocol_version=None):
 
174
        http_server.HttpServer.__init__(self, request_handler,
 
175
                                        protocol_version=protocol_version)
255
176
        # redirections is a list of tuples (source, target, code)
256
177
        # - source is a regexp for the paths requested
257
178
        # - target is a replacement for re.sub describing where
309
230
       self.old_server = self.get_secondary_server()
310
231
 
311
232
 
 
233
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
 
234
    """Requires an authentication to process requests.
 
235
 
 
236
    This is intended to be used with a server that always and
 
237
    only use one authentication scheme (implemented by daughter
 
238
    classes).
 
239
    """
 
240
 
 
241
    # The following attributes should be defined in the server
 
242
    # - auth_header_sent: the header name sent to require auth
 
243
    # - auth_header_recv: the header received containing auth
 
244
    # - auth_error_code: the error code to indicate auth required
 
245
 
 
246
    def do_GET(self):
 
247
        if self.authorized():
 
248
            return http_server.TestingHTTPRequestHandler.do_GET(self)
 
249
        else:
 
250
            # Note that we must update test_case_server *before*
 
251
            # sending the error or the client may try to read it
 
252
            # before we have sent the whole error back.
 
253
            tcs = self.server.test_case_server
 
254
            tcs.auth_required_errors += 1
 
255
            self.send_response(tcs.auth_error_code)
 
256
            self.send_header_auth_reqed()
 
257
            # We do not send a body
 
258
            self.send_header('Content-Length', '0')
 
259
            self.end_headers()
 
260
            return
 
261
 
 
262
 
 
263
class BasicAuthRequestHandler(AuthRequestHandler):
 
264
    """Implements the basic authentication of a request"""
 
265
 
 
266
    def authorized(self):
 
267
        tcs = self.server.test_case_server
 
268
        if tcs.auth_scheme != 'basic':
 
269
            return False
 
270
 
 
271
        auth_header = self.headers.get(tcs.auth_header_recv, None)
 
272
        if auth_header:
 
273
            scheme, raw_auth = auth_header.split(' ', 1)
 
274
            if scheme.lower() == tcs.auth_scheme:
 
275
                user, password = raw_auth.decode('base64').split(':')
 
276
                return tcs.authorized(user, password)
 
277
 
 
278
        return False
 
279
 
 
280
    def send_header_auth_reqed(self):
 
281
        tcs = self.server.test_case_server
 
282
        self.send_header(tcs.auth_header_sent,
 
283
                         'Basic realm="%s"' % tcs.auth_realm)
 
284
 
 
285
 
 
286
# FIXME: We could send an Authentication-Info header too when
 
287
# the authentication is succesful
 
288
 
 
289
class DigestAuthRequestHandler(AuthRequestHandler):
 
290
    """Implements the digest authentication of a request.
 
291
 
 
292
    We need persistence for some attributes and that can't be
 
293
    achieved here since we get instantiated for each request. We
 
294
    rely on the DigestAuthServer to take care of them.
 
295
    """
 
296
 
 
297
    def authorized(self):
 
298
        tcs = self.server.test_case_server
 
299
 
 
300
        auth_header = self.headers.get(tcs.auth_header_recv, None)
 
301
        if auth_header is None:
 
302
            return False
 
303
        scheme, auth = auth_header.split(None, 1)
 
304
        if scheme.lower() == tcs.auth_scheme:
 
305
            auth_dict = urllib2.parse_keqv_list(urllib2.parse_http_list(auth))
 
306
 
 
307
            return tcs.digest_authorized(auth_dict, self.command)
 
308
 
 
309
        return False
 
310
 
 
311
    def send_header_auth_reqed(self):
 
312
        tcs = self.server.test_case_server
 
313
        header = 'Digest realm="%s", ' % tcs.auth_realm
 
314
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
 
315
                                                              'MD5')
 
316
        self.send_header(tcs.auth_header_sent,header)
 
317
 
 
318
 
 
319
class DigestAndBasicAuthRequestHandler(DigestAuthRequestHandler):
 
320
    """Implements a digest and basic authentication of a request.
 
321
 
 
322
    I.e. the server proposes both schemes and the client should choose the best
 
323
    one it can handle, which, in that case, should be digest, the only scheme
 
324
    accepted here.
 
325
    """
 
326
 
 
327
    def send_header_auth_reqed(self):
 
328
        tcs = self.server.test_case_server
 
329
        self.send_header(tcs.auth_header_sent,
 
330
                         'Basic realm="%s"' % tcs.auth_realm)
 
331
        header = 'Digest realm="%s", ' % tcs.auth_realm
 
332
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
 
333
                                                              'MD5')
 
334
        self.send_header(tcs.auth_header_sent,header)
 
335
 
 
336
 
 
337
class AuthServer(http_server.HttpServer):
 
338
    """Extends HttpServer with a dictionary of passwords.
 
339
 
 
340
    This is used as a base class for various schemes which should
 
341
    all use or redefined the associated AuthRequestHandler.
 
342
 
 
343
    Note that no users are defined by default, so add_user should
 
344
    be called before issuing the first request.
 
345
    """
 
346
 
 
347
    # The following attributes should be set dy daughter classes
 
348
    # and are used by AuthRequestHandler.
 
349
    auth_header_sent = None
 
350
    auth_header_recv = None
 
351
    auth_error_code = None
 
352
    auth_realm = "Thou should not pass"
 
353
 
 
354
    def __init__(self, request_handler, auth_scheme,
 
355
                 protocol_version=None):
 
356
        http_server.HttpServer.__init__(self, request_handler,
 
357
                                        protocol_version=protocol_version)
 
358
        self.auth_scheme = auth_scheme
 
359
        self.password_of = {}
 
360
        self.auth_required_errors = 0
 
361
 
 
362
    def add_user(self, user, password):
 
363
        """Declare a user with an associated password.
 
364
 
 
365
        password can be empty, use an empty string ('') in that
 
366
        case, not None.
 
367
        """
 
368
        self.password_of[user] = password
 
369
 
 
370
    def authorized(self, user, password):
 
371
        """Check that the given user provided the right password"""
 
372
        expected_password = self.password_of.get(user, None)
 
373
        return expected_password is not None and password == expected_password
 
374
 
 
375
 
 
376
# FIXME: There is some code duplication with
 
377
# _urllib2_wrappers.py.DigestAuthHandler. If that duplication
 
378
# grows, it may require a refactoring. Also, we don't implement
 
379
# SHA algorithm nor MD5-sess here, but that does not seem worth
 
380
# it.
 
381
class DigestAuthServer(AuthServer):
 
382
    """A digest authentication server"""
 
383
 
 
384
    auth_nonce = 'now!'
 
385
 
 
386
    def __init__(self, request_handler, auth_scheme,
 
387
                 protocol_version=None):
 
388
        AuthServer.__init__(self, request_handler, auth_scheme,
 
389
                            protocol_version=protocol_version)
 
390
 
 
391
    def digest_authorized(self, auth, command):
 
392
        nonce = auth['nonce']
 
393
        if nonce != self.auth_nonce:
 
394
            return False
 
395
        realm = auth['realm']
 
396
        if realm != self.auth_realm:
 
397
            return False
 
398
        user = auth['username']
 
399
        if not self.password_of.has_key(user):
 
400
            return False
 
401
        algorithm= auth['algorithm']
 
402
        if algorithm != 'MD5':
 
403
            return False
 
404
        qop = auth['qop']
 
405
        if qop != 'auth':
 
406
            return False
 
407
 
 
408
        password = self.password_of[user]
 
409
 
 
410
        # Recalculate the response_digest to compare with the one
 
411
        # sent by the client
 
412
        A1 = '%s:%s:%s' % (user, realm, password)
 
413
        A2 = '%s:%s' % (command, auth['uri'])
 
414
 
 
415
        H = lambda x: osutils.md5(x).hexdigest()
 
416
        KD = lambda secret, data: H("%s:%s" % (secret, data))
 
417
 
 
418
        nonce_count = int(auth['nc'], 16)
 
419
 
 
420
        ncvalue = '%08x' % nonce_count
 
421
 
 
422
        cnonce = auth['cnonce']
 
423
        noncebit = '%s:%s:%s:%s:%s' % (nonce, ncvalue, cnonce, qop, H(A2))
 
424
        response_digest = KD(H(A1), noncebit)
 
425
 
 
426
        return response_digest == auth['response']
 
427
 
 
428
 
 
429
class HTTPAuthServer(AuthServer):
 
430
    """An HTTP server requiring authentication"""
 
431
 
 
432
    def init_http_auth(self):
 
433
        self.auth_header_sent = 'WWW-Authenticate'
 
434
        self.auth_header_recv = 'Authorization'
 
435
        self.auth_error_code = 401
 
436
 
 
437
 
 
438
class ProxyAuthServer(AuthServer):
 
439
    """A proxy server requiring authentication"""
 
440
 
 
441
    def init_proxy_auth(self):
 
442
        self.proxy_requests = True
 
443
        self.auth_header_sent = 'Proxy-Authenticate'
 
444
        self.auth_header_recv = 'Proxy-Authorization'
 
445
        self.auth_error_code = 407
 
446
 
 
447
 
 
448
class HTTPBasicAuthServer(HTTPAuthServer):
 
449
    """An HTTP server requiring basic authentication"""
 
450
 
 
451
    def __init__(self, protocol_version=None):
 
452
        HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
 
453
                                protocol_version=protocol_version)
 
454
        self.init_http_auth()
 
455
 
 
456
 
 
457
class HTTPDigestAuthServer(DigestAuthServer, HTTPAuthServer):
 
458
    """An HTTP server requiring digest authentication"""
 
459
 
 
460
    def __init__(self, protocol_version=None):
 
461
        DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
 
462
                                  protocol_version=protocol_version)
 
463
        self.init_http_auth()
 
464
 
 
465
 
 
466
class HTTPBasicAndDigestAuthServer(DigestAuthServer, HTTPAuthServer):
 
467
    """An HTTP server requiring basic or digest authentication"""
 
468
 
 
469
    def __init__(self, protocol_version=None):
 
470
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
 
471
                                  'basicdigest',
 
472
                                  protocol_version=protocol_version)
 
473
        self.init_http_auth()
 
474
        # We really accept Digest only
 
475
        self.auth_scheme = 'digest'
 
476
 
 
477
 
 
478
class ProxyBasicAuthServer(ProxyAuthServer):
 
479
    """A proxy server requiring basic authentication"""
 
480
 
 
481
    def __init__(self, protocol_version=None):
 
482
        ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
 
483
                                 protocol_version=protocol_version)
 
484
        self.init_proxy_auth()
 
485
 
 
486
 
 
487
class ProxyDigestAuthServer(DigestAuthServer, ProxyAuthServer):
 
488
    """A proxy server requiring basic authentication"""
 
489
 
 
490
    def __init__(self, protocol_version=None):
 
491
        ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
 
492
                                 protocol_version=protocol_version)
 
493
        self.init_proxy_auth()
 
494
 
 
495
 
 
496
class ProxyBasicAndDigestAuthServer(DigestAuthServer, ProxyAuthServer):
 
497
    """An proxy server requiring basic or digest authentication"""
 
498
 
 
499
    def __init__(self, protocol_version=None):
 
500
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
 
501
                                  'basicdigest',
 
502
                                  protocol_version=protocol_version)
 
503
        self.init_proxy_auth()
 
504
        # We really accept Digest only
 
505
        self.auth_scheme = 'digest'
 
506
 
 
507