~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/http_utils.py

  • Committer: Jelmer Vernooij
  • Date: 2008-06-11 18:58:19 UTC
  • mto: (3649.3.2 bzr.dev)
  • mto: This revision was merged to the branch mainline in revision 3658.
  • Revision ID: jelmer@samba.org-20080611185819-o4shi1ranh9zh01e
Move ftp transport into separate directory.

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 md5
19
20
import re
 
21
import sha
20
22
import socket
21
23
import threading
22
24
import time
25
27
 
26
28
 
27
29
from bzrlib import (
28
 
    errors,
29
 
    osutils,
30
30
    tests,
31
31
    transport,
32
32
    )
33
 
from bzrlib.smart import (
34
 
    medium,
35
 
    protocol,
36
 
    )
 
33
from bzrlib.smart import protocol
37
34
from bzrlib.tests import http_server
38
 
from bzrlib.transport import chroot
39
35
 
40
36
 
41
37
class HTTPServerWithSmarts(http_server.HttpServer):
50
46
 
51
47
 
52
48
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
53
 
    """Extend TestingHTTPRequestHandler to support smart client POSTs.
54
 
 
55
 
    XXX: This duplicates a fair bit of the logic in bzrlib.transport.http.wsgi.
56
 
    """
 
49
    """Extend TestingHTTPRequestHandler to support smart client POSTs."""
57
50
 
58
51
    def do_POST(self):
59
52
        """Hand the request off to a smart server instance."""
60
 
        backing = transport.get_transport(
61
 
            self.server.test_case_server._home_dir)
62
 
        chroot_server = chroot.ChrootServer(backing)
63
 
        chroot_server.start_server()
64
 
        try:
65
 
            t = transport.get_transport(chroot_server.get_url())
66
 
            self.do_POST_inner(t)
67
 
        finally:
68
 
            chroot_server.stop_server()
69
 
 
70
 
    def do_POST_inner(self, chrooted_transport):
71
53
        self.send_response(200)
72
54
        self.send_header("Content-type", "application/octet-stream")
73
 
        if not self.path.endswith('.bzr/smart'):
74
 
            raise AssertionError(
75
 
                'POST to path not ending in .bzr/smart: %r' % (self.path,))
76
 
        t = chrooted_transport.clone(self.path[:-len('.bzr/smart')])
77
 
        # if this fails, we should return 400 bad request, but failure is
78
 
        # failure for now - RBC 20060919
79
 
        data_length = int(self.headers['Content-Length'])
 
55
        t = transport.get_transport(self.server.test_case_server._home_dir)
80
56
        # TODO: We might like to support streaming responses.  1.0 allows no
81
57
        # Content-length in this case, so for integrity we should perform our
82
58
        # own chunking within the stream.
84
60
        # the HTTP chunking as this will allow HTTP persistence safely, even if
85
61
        # we have to stop early due to error, but we would also have to use the
86
62
        # HTTP trailer facility which may not be widely available.
87
 
        request_bytes = self.rfile.read(data_length)
88
 
        protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
89
 
            request_bytes)
90
63
        out_buffer = StringIO()
91
 
        smart_protocol_request = protocol_factory(t, out_buffer.write, '/')
 
64
        smart_protocol_request = protocol.SmartServerRequestProtocolOne(
 
65
                t, out_buffer.write)
 
66
        # if this fails, we should return 400 bad request, but failure is
 
67
        # failure for now - RBC 20060919
 
68
        data_length = int(self.headers['Content-Length'])
92
69
        # Perhaps there should be a SmartServerHTTPMedium that takes care of
93
70
        # feeding the bytes in the http request to the smart_protocol_request,
94
71
        # but for now it's simpler to just feed the bytes directly.
95
 
        smart_protocol_request.accept_bytes(unused_bytes)
96
 
        if not (smart_protocol_request.next_read_size() == 0):
97
 
            raise errors.SmartProtocolError(
98
 
                "not finished reading, but all data sent to protocol.")
 
72
        smart_protocol_request.accept_bytes(self.rfile.read(data_length))
 
73
        assert smart_protocol_request.next_read_size() == 0, (
 
74
            "not finished reading, but all data sent to protocol.")
99
75
        self.send_header("Content-Length", str(len(out_buffer.getvalue())))
100
76
        self.end_headers()
101
77
        self.wfile.write(out_buffer.getvalue())
108
84
    one. This will currently fail if the primary transport is not
109
85
    backed by regular disk files.
110
86
    """
111
 
 
112
 
    # These attributes can be overriden or parametrized by daughter clasess if
113
 
    # needed, but must exist so that the create_transport_readonly_server()
114
 
    # method (or any method creating an http(s) server) can propagate it.
115
 
    _protocol_version = None
116
 
    _url_protocol = 'http'
117
 
 
118
87
    def setUp(self):
119
88
        super(TestCaseWithWebserver, self).setUp()
120
89
        self.transport_readonly_server = http_server.HttpServer
121
90
 
122
 
    def create_transport_readonly_server(self):
123
 
        server = self.transport_readonly_server(
124
 
            protocol_version=self._protocol_version)
125
 
        server._url_protocol = self._url_protocol
126
 
        return server
127
 
 
128
91
 
129
92
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
130
93
    """A support class providing readonly urls on two servers that are http://.
142
105
 
143
106
        This is mostly a hook for daughter classes.
144
107
        """
145
 
        server = self.transport_secondary_server(
146
 
            protocol_version=self._protocol_version)
147
 
        server._url_protocol = self._url_protocol
148
 
        return server
 
108
        return self.transport_secondary_server()
149
109
 
150
110
    def get_secondary_server(self):
151
111
        """Get the server instance for the secondary transport."""
152
112
        if self.__secondary_server is None:
153
113
            self.__secondary_server = self.create_transport_secondary_server()
154
 
            self.start_server(self.__secondary_server)
 
114
            self.__secondary_server.setUp()
 
115
            self.addCleanup(self.__secondary_server.tearDown)
155
116
        return self.__secondary_server
156
117
 
157
 
    def get_secondary_url(self, relpath=None):
158
 
        base = self.get_secondary_server().get_url()
159
 
        return self._adjust_url(base, relpath)
160
 
 
161
 
    def get_secondary_transport(self, relpath=None):
162
 
        t = transport.get_transport(self.get_secondary_url(relpath))
163
 
        self.assertTrue(t.is_readonly())
164
 
        return t
165
 
 
166
118
 
167
119
class ProxyServer(http_server.HttpServer):
168
120
    """A proxy test server for http transports."""
242
194
   The 'old' server is redirected to the 'new' server.
243
195
   """
244
196
 
 
197
   def create_transport_secondary_server(self):
 
198
       """Create the secondary server redirecting to the primary server"""
 
199
       new = self.get_readonly_server()
 
200
       redirecting = HTTPServerRedirecting()
 
201
       redirecting.redirect_to(new.host, new.port)
 
202
       return redirecting
 
203
 
245
204
   def setUp(self):
246
205
       super(TestCaseWithRedirectedWebserver, self).setUp()
247
206
       # The redirections will point to the new server
248
207
       self.new_server = self.get_readonly_server()
249
 
       # The requests to the old server will be redirected to the new server
 
208
       # The requests to the old server will be redirected
250
209
       self.old_server = self.get_secondary_server()
251
210
 
252
 
   def create_transport_secondary_server(self):
253
 
       """Create the secondary server redirecting to the primary server"""
254
 
       new = self.get_readonly_server()
255
 
       redirecting = HTTPServerRedirecting(
256
 
           protocol_version=self._protocol_version)
257
 
       redirecting.redirect_to(new.host, new.port)
258
 
       redirecting._url_protocol = self._url_protocol
259
 
       return redirecting
260
 
 
261
 
   def get_old_url(self, relpath=None):
262
 
        base = self.old_server.get_url()
263
 
        return self._adjust_url(base, relpath)
264
 
 
265
 
   def get_old_transport(self, relpath=None):
266
 
        t = transport.get_transport(self.get_old_url(relpath))
267
 
        self.assertTrue(t.is_readonly())
268
 
        return t
269
 
 
270
 
   def get_new_url(self, relpath=None):
271
 
        base = self.new_server.get_url()
272
 
        return self._adjust_url(base, relpath)
273
 
 
274
 
   def get_new_transport(self, relpath=None):
275
 
        t = transport.get_transport(self.get_new_url(relpath))
276
 
        self.assertTrue(t.is_readonly())
277
 
        return t
278
 
 
279
211
 
280
212
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
281
213
    """Requires an authentication to process requests.
343
275
 
344
276
    def authorized(self):
345
277
        tcs = self.server.test_case_server
 
278
        if tcs.auth_scheme != 'digest':
 
279
            return False
346
280
 
347
281
        auth_header = self.headers.get(tcs.auth_header_recv, None)
348
282
        if auth_header is None:
363
297
        self.send_header(tcs.auth_header_sent,header)
364
298
 
365
299
 
366
 
class DigestAndBasicAuthRequestHandler(DigestAuthRequestHandler):
367
 
    """Implements a digest and basic authentication of a request.
368
 
 
369
 
    I.e. the server proposes both schemes and the client should choose the best
370
 
    one it can handle, which, in that case, should be digest, the only scheme
371
 
    accepted here.
372
 
    """
373
 
 
374
 
    def send_header_auth_reqed(self):
375
 
        tcs = self.server.test_case_server
376
 
        self.send_header(tcs.auth_header_sent,
377
 
                         'Basic realm="%s"' % tcs.auth_realm)
378
 
        header = 'Digest realm="%s", ' % tcs.auth_realm
379
 
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
380
 
                                                              'MD5')
381
 
        self.send_header(tcs.auth_header_sent,header)
382
 
 
383
 
 
384
300
class AuthServer(http_server.HttpServer):
385
301
    """Extends HttpServer with a dictionary of passwords.
386
302
 
459
375
        A1 = '%s:%s:%s' % (user, realm, password)
460
376
        A2 = '%s:%s' % (command, auth['uri'])
461
377
 
462
 
        H = lambda x: osutils.md5(x).hexdigest()
 
378
        H = lambda x: md5.new(x).hexdigest()
463
379
        KD = lambda secret, data: H("%s:%s" % (secret, data))
464
380
 
465
381
        nonce_count = int(auth['nc'], 16)
472
388
 
473
389
        return response_digest == auth['response']
474
390
 
475
 
 
476
391
class HTTPAuthServer(AuthServer):
477
392
    """An HTTP server requiring authentication"""
478
393
 
510
425
        self.init_http_auth()
511
426
 
512
427
 
513
 
class HTTPBasicAndDigestAuthServer(DigestAuthServer, HTTPAuthServer):
514
 
    """An HTTP server requiring basic or digest authentication"""
515
 
 
516
 
    def __init__(self, protocol_version=None):
517
 
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
518
 
                                  'basicdigest',
519
 
                                  protocol_version=protocol_version)
520
 
        self.init_http_auth()
521
 
        # We really accept Digest only
522
 
        self.auth_scheme = 'digest'
523
 
 
524
 
 
525
428
class ProxyBasicAuthServer(ProxyAuthServer):
526
429
    """A proxy server requiring basic authentication"""
527
430
 
540
443
        self.init_proxy_auth()
541
444
 
542
445
 
543
 
class ProxyBasicAndDigestAuthServer(DigestAuthServer, ProxyAuthServer):
544
 
    """An proxy server requiring basic or digest authentication"""
545
 
 
546
 
    def __init__(self, protocol_version=None):
547
 
        DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
548
 
                                  'basicdigest',
549
 
                                  protocol_version=protocol_version)
550
 
        self.init_proxy_auth()
551
 
        # We really accept Digest only
552
 
        self.auth_scheme = 'digest'
553
 
 
554