~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/HTTPTestUtil.py

  • Committer: Martin Pool
  • Date: 2007-01-24 07:12:09 UTC
  • mto: This revision was merged to the branch mainline in revision 2244.
  • Revision ID: mbp@sourcefrog.net-20070124071209-yqiths20n6wxqaqr
Change RepositoryFormat to use a Registry rather than ad-hoc dictionary

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 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
18
import errno
19
 
import re
 
19
from SimpleHTTPServer import SimpleHTTPRequestHandler
20
20
import socket
21
 
import threading
22
 
import time
23
 
import urllib2
24
21
import urlparse
25
22
 
26
 
 
27
 
from bzrlib import (
28
 
    errors,
29
 
    osutils,
30
 
    tests,
 
23
from bzrlib.tests import TestCaseWithTransport
 
24
from bzrlib.tests.HttpServer import (
 
25
    HttpServer,
 
26
    TestingHTTPRequestHandler,
31
27
    )
32
 
from bzrlib.smart import medium, protocol
33
 
from bzrlib.tests import http_server
34
28
from bzrlib.transport import (
35
 
    chroot,
36
29
    get_transport,
 
30
    smart,
37
31
    )
38
32
 
39
33
 
40
 
class HTTPServerWithSmarts(http_server.HttpServer):
 
34
class WallRequestHandler(TestingHTTPRequestHandler):
 
35
    """Whatever request comes in, close the connection"""
 
36
 
 
37
    def handle_one_request(self):
 
38
        """Handle a single HTTP request, by abruptly closing the connection"""
 
39
        self.close_connection = 1
 
40
 
 
41
 
 
42
class BadStatusRequestHandler(TestingHTTPRequestHandler):
 
43
    """Whatever request comes in, returns a bad status"""
 
44
 
 
45
    def parse_request(self):
 
46
        """Fakes handling a single HTTP request, returns a bad status"""
 
47
        ignored = TestingHTTPRequestHandler.parse_request(self)
 
48
        try:
 
49
            self.send_response(0, "Bad status")
 
50
            self.end_headers()
 
51
        except socket.error, e:
 
52
            # We don't want to pollute the test results with
 
53
            # spurious server errors while test succeed. In our
 
54
            # case, it may occur that the test has already read
 
55
            # the 'Bad Status' and closed the socket while we are
 
56
            # still trying to send some headers... So the test is
 
57
            # ok, but if we raise the exception, the output is
 
58
            # dirty. So we don't raise, but we close the
 
59
            # connection, just to be safe :)
 
60
            spurious = [errno.EPIPE,
 
61
                        errno.ECONNRESET,
 
62
                        errno.ECONNABORTED,
 
63
                        ]
 
64
            if (len(e.args) > 0) and (e.args[0] in spurious):
 
65
                self.close_connection = 1
 
66
                pass
 
67
            else:
 
68
                raise
 
69
        return False
 
70
 
 
71
 
 
72
class InvalidStatusRequestHandler(TestingHTTPRequestHandler):
 
73
    """Whatever request comes in, returns am invalid status"""
 
74
 
 
75
    def parse_request(self):
 
76
        """Fakes handling a single HTTP request, returns a bad status"""
 
77
        ignored = TestingHTTPRequestHandler.parse_request(self)
 
78
        self.wfile.write("Invalid status line\r\n")
 
79
        return False
 
80
 
 
81
 
 
82
class BadProtocolRequestHandler(TestingHTTPRequestHandler):
 
83
    """Whatever request comes in, returns a bad protocol version"""
 
84
 
 
85
    def parse_request(self):
 
86
        """Fakes handling a single HTTP request, returns a bad status"""
 
87
        ignored = TestingHTTPRequestHandler.parse_request(self)
 
88
        # Returns an invalid protocol version, but curl just
 
89
        # ignores it and those cannot be tested.
 
90
        self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
 
91
                                           404,
 
92
                                           'Look at my protocol version'))
 
93
        return False
 
94
 
 
95
 
 
96
class ForbiddenRequestHandler(TestingHTTPRequestHandler):
 
97
    """Whatever request comes in, returns a 403 code"""
 
98
 
 
99
    def parse_request(self):
 
100
        """Handle a single HTTP request, by replying we cannot handle it"""
 
101
        ignored = TestingHTTPRequestHandler.parse_request(self)
 
102
        self.send_error(403)
 
103
        return False
 
104
 
 
105
 
 
106
class HTTPServerWithSmarts(HttpServer):
41
107
    """HTTPServerWithSmarts extends the HttpServer with POST methods that will
42
108
    trigger a smart server to execute with a transport rooted at the rootdir of
43
109
    the HTTP server.
44
110
    """
45
111
 
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
 
    """
 
112
    def __init__(self):
 
113
        HttpServer.__init__(self, SmartRequestHandler)
 
114
 
 
115
 
 
116
class SmartRequestHandler(TestingHTTPRequestHandler):
 
117
    """Extend TestingHTTPRequestHandler to support smart client POSTs."""
56
118
 
57
119
    def do_POST(self):
58
120
        """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):
69
121
        self.send_response(200)
70
122
        self.send_header("Content-type", "application/octet-stream")
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'])
 
123
        transport = get_transport(self.server.test_case._home_dir)
78
124
        # TODO: We might like to support streaming responses.  1.0 allows no
79
125
        # Content-length in this case, so for integrity we should perform our
80
126
        # own chunking within the stream.
82
128
        # the HTTP chunking as this will allow HTTP persistence safely, even if
83
129
        # we have to stop early due to error, but we would also have to use the
84
130
        # 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)
88
131
        out_buffer = StringIO()
89
 
        smart_protocol_request = protocol_factory(t, out_buffer.write, '/')
 
132
        smart_protocol_request = smart.SmartServerRequestProtocolOne(
 
133
                transport, out_buffer.write)
 
134
        # if this fails, we should return 400 bad request, but failure is
 
135
        # failure for now - RBC 20060919
 
136
        data_length = int(self.headers['Content-Length'])
90
137
        # Perhaps there should be a SmartServerHTTPMedium that takes care of
91
138
        # feeding the bytes in the http request to the smart_protocol_request,
92
139
        # but for now it's simpler to just feed the bytes directly.
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.")
 
140
        smart_protocol_request.accept_bytes(self.rfile.read(data_length))
 
141
        assert smart_protocol_request.next_read_size() == 0, (
 
142
            "not finished reading, but all data sent to protocol.")
97
143
        self.send_header("Content-Length", str(len(out_buffer.getvalue())))
98
144
        self.end_headers()
99
145
        self.wfile.write(out_buffer.getvalue())
100
146
 
101
147
 
102
 
class TestCaseWithWebserver(tests.TestCaseWithTransport):
 
148
class SingleRangeRequestHandler(TestingHTTPRequestHandler):
 
149
    """Always reply to range request as if they were single.
 
150
 
 
151
    Don't be explicit about it, just to annoy the clients.
 
152
    """
 
153
 
 
154
    def get_multiple_ranges(self, file, file_size, ranges):
 
155
        """Answer as if it was a single range request and ignores the rest"""
 
156
        (start, end) = ranges[0]
 
157
        return self.get_single_range(file, file_size, start, end)
 
158
 
 
159
 
 
160
class NoRangeRequestHandler(TestingHTTPRequestHandler):
 
161
    """Ignore range requests without notice"""
 
162
 
 
163
    # Just bypass the range handling done by TestingHTTPRequestHandler
 
164
    do_GET = SimpleHTTPRequestHandler.do_GET
 
165
 
 
166
 
 
167
class TestCaseWithWebserver(TestCaseWithTransport):
103
168
    """A support class that provides readonly urls that are http://.
104
169
 
105
170
    This is done by forcing the readonly server to be an http
108
173
    """
109
174
    def setUp(self):
110
175
        super(TestCaseWithWebserver, self).setUp()
111
 
        self.transport_readonly_server = http_server.HttpServer
 
176
        self.transport_readonly_server = HttpServer
112
177
 
113
178
 
114
179
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
115
 
    """A support class providing readonly urls on two servers that are http://.
 
180
    """A support class providinf readonly urls (on two servers) that are http://.
116
181
 
117
 
    We set up two webservers to allows various tests involving
 
182
    We setup two webservers to allows various tests involving
118
183
    proxies or redirections from one server to the other.
119
184
    """
120
185
    def setUp(self):
121
186
        super(TestCaseWithTwoWebservers, self).setUp()
122
 
        self.transport_secondary_server = http_server.HttpServer
 
187
        self.transport_secondary_server = HttpServer
123
188
        self.__secondary_server = None
124
189
 
125
190
    def create_transport_secondary_server(self):
133
198
        """Get the server instance for the secondary transport."""
134
199
        if self.__secondary_server is None:
135
200
            self.__secondary_server = self.create_transport_secondary_server()
136
 
            self.start_server(self.__secondary_server)
 
201
            self.__secondary_server.setUp()
 
202
            self.addCleanup(self.__secondary_server.tearDown)
137
203
        return self.__secondary_server
138
204
 
139
205
 
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):
147
 
    """Redirect all request to the specified server"""
148
 
 
149
 
    def parse_request(self):
150
 
        """Redirect a single HTTP request to another host"""
151
 
        valid = http_server.TestingHTTPRequestHandler.parse_request(self)
152
 
        if valid:
153
 
            tcs = self.server.test_case_server
154
 
            code, target = tcs.is_redirected(self.path)
155
 
            if code is not None and target is not None:
156
 
                # Redirect as instructed
157
 
                self.send_response(code)
158
 
                self.send_header('Location', target)
159
 
                # We do not send a body
160
 
                self.send_header('Content-Length', '0')
161
 
                self.end_headers()
162
 
                return False # The job is done
163
 
            else:
164
 
                # We leave the parent class serve the request
165
 
                pass
166
 
        return valid
167
 
 
168
 
 
169
 
class HTTPServerRedirecting(http_server.HttpServer):
170
 
    """An HttpServer redirecting to another server """
171
 
 
172
 
    def __init__(self, request_handler=RedirectRequestHandler,
173
 
                 protocol_version=None):
174
 
        http_server.HttpServer.__init__(self, request_handler,
175
 
                                        protocol_version=protocol_version)
176
 
        # redirections is a list of tuples (source, target, code)
177
 
        # - source is a regexp for the paths requested
178
 
        # - target is a replacement for re.sub describing where
179
 
        #   the request will be redirected
180
 
        # - code is the http error code associated to the
181
 
        #   redirection (301 permanent, 302 temporarry, etc
182
 
        self.redirections = []
183
 
 
184
 
    def redirect_to(self, host, port):
185
 
        """Redirect all requests to a specific host:port"""
186
 
        self.redirections = [('(.*)',
187
 
                              r'http://%s:%s\1' % (host, port) ,
188
 
                              301)]
189
 
 
190
 
    def is_redirected(self, path):
191
 
        """Is the path redirected by this server.
192
 
 
193
 
        :param path: the requested relative path
194
 
 
195
 
        :returns: a tuple (code, target) if a matching
196
 
             redirection is found, (None, None) otherwise.
197
 
        """
198
 
        code = None
199
 
        target = None
200
 
        for (rsource, rtarget, rcode) in self.redirections:
201
 
            target, match = re.subn(rsource, rtarget, path)
202
 
            if match:
203
 
                code = rcode
204
 
                break # The first match wins
205
 
            else:
206
 
                target = None
207
 
        return code, target
208
 
 
209
 
 
210
 
class TestCaseWithRedirectedWebserver(TestCaseWithTwoWebservers):
211
 
   """A support class providing redirections from one server to another.
212
 
 
213
 
   We set up two webservers to allows various tests involving
214
 
   redirections.
215
 
   The 'old' server is redirected to the 'new' server.
216
 
   """
217
 
 
218
 
   def create_transport_secondary_server(self):
219
 
       """Create the secondary server redirecting to the primary server"""
220
 
       new = self.get_readonly_server()
221
 
       redirecting = HTTPServerRedirecting()
222
 
       redirecting.redirect_to(new.host, new.port)
223
 
       return redirecting
224
 
 
225
 
   def setUp(self):
226
 
       super(TestCaseWithRedirectedWebserver, self).setUp()
227
 
       # The redirections will point to the new server
228
 
       self.new_server = self.get_readonly_server()
229
 
       # The requests to the old server will be redirected
230
 
       self.old_server = self.get_secondary_server()
231
 
 
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'
 
206
class FakeProxyRequestHandler(TestingHTTPRequestHandler):
 
207
    """Append a '-proxied' suffix to file served"""
 
208
 
 
209
    def translate_path(self, path):
 
210
        # We need to act as a proxy and accept absolute urls,
 
211
        # which SimpleHTTPRequestHandler (grand parent) is not
 
212
        # ready for. So we just drop the protocol://host:port
 
213
        # part in front of the request-url (because we know we
 
214
        # would not forward the request to *another* proxy).
 
215
 
 
216
        # So we do what SimpleHTTPRequestHandler.translate_path
 
217
        # do beginning with python 2.4.3: abandon query
 
218
        # parameters, scheme, host port, etc (which ensure we
 
219
        # provide the right behaviour on all python versions).
 
220
        path = urlparse.urlparse(path)[2]
 
221
        # And now, we can apply *our* trick to proxy files
 
222
        self.path += '-proxied'
 
223
        # An finally we leave our mother class do whatever it
 
224
        # wants with the path
 
225
        return TestingHTTPRequestHandler.translate_path(self, path)
 
226
 
506
227
 
507
228