/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: Robert Collins
  • Date: 2005-12-24 02:20:45 UTC
  • mto: (1185.50.57 bzr-jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1550.
  • Revision ID: robertc@robertcollins.net-20051224022045-14efc8dfa0e1a4e9
Start tests for api usage.

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