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

[merge] robertc's integration, updated tests to check for retcode=3

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):
111
 
    """A support class that provides readonly urls that are http://.
112
 
 
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.
116
 
    """
117
 
 
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
 
    def setUp(self):
125
 
        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.
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import BaseHTTPServer, SimpleHTTPServer, socket, errno, time
 
18
from bzrlib.selftest 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
 
151
42
        """
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
 
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] == errno.EAGAIN:
 
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:
199
114
                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
 
    """
 
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
251
131
 
252
132
    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'
 
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