/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 breezy/tests/http_utils.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2020-08-23 01:15:41 UTC
  • mfrom: (7520.1.4 merge-3.1)
  • Revision ID: breezy.the.bot@gmail.com-20200823011541-nv0oh7nzaganx2qy
Merge lp:brz/3.1.

Merged from https://code.launchpad.net/~jelmer/brz/merge-3.1/+merge/389690

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from cStringIO import StringIO
18
 
import errno
 
17
import base64
 
18
from io import BytesIO
19
19
import re
20
 
import socket
21
 
import threading
22
 
import time
23
 
import urllib2
24
 
import urlparse
25
 
 
26
 
 
27
 
from bzrlib import (
 
20
from urllib.request import (
 
21
    parse_http_list,
 
22
    parse_keqv_list,
 
23
    )
 
24
 
 
25
 
 
26
from .. import (
28
27
    errors,
29
28
    osutils,
30
29
    tests,
31
 
    )
32
 
from bzrlib.smart import medium, protocol
33
 
from bzrlib.tests import http_server
34
 
from bzrlib.transport import (
35
 
    chroot,
36
 
    get_transport,
37
 
    )
 
30
    transport,
 
31
    )
 
32
from ..bzr.smart import (
 
33
    medium,
 
34
    )
 
35
from . import http_server
 
36
from ..transport import chroot
38
37
 
39
38
 
40
39
class HTTPServerWithSmarts(http_server.HttpServer):
51
50
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
52
51
    """Extend TestingHTTPRequestHandler to support smart client POSTs.
53
52
 
54
 
    XXX: This duplicates a fair bit of the logic in bzrlib.transport.http.wsgi.
 
53
    XXX: This duplicates a fair bit of the logic in breezy.transport.http.wsgi.
55
54
    """
56
55
 
57
56
    def do_POST(self):
58
57
        """Hand the request off to a smart server instance."""
59
 
        backing = get_transport(self.server.test_case_server._home_dir)
 
58
        backing = transport.get_transport_from_path(
 
59
            self.server.test_case_server._home_dir)
60
60
        chroot_server = chroot.ChrootServer(backing)
61
61
        chroot_server.start_server()
62
62
        try:
63
 
            t = get_transport(chroot_server.get_url())
 
63
            t = transport.get_transport_from_url(chroot_server.get_url())
64
64
            self.do_POST_inner(t)
65
65
        finally:
66
66
            chroot_server.stop_server()
83
83
        # we have to stop early due to error, but we would also have to use the
84
84
        # HTTP trailer facility which may not be widely available.
85
85
        request_bytes = self.rfile.read(data_length)
86
 
        protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
87
 
            request_bytes)
88
 
        out_buffer = StringIO()
 
86
        protocol_factory, unused_bytes = (
 
87
            medium._get_protocol_factory_for_bytes(request_bytes))
 
88
        out_buffer = BytesIO()
89
89
        smart_protocol_request = protocol_factory(t, out_buffer.write, '/')
90
90
        # Perhaps there should be a SmartServerHTTPMedium that takes care of
91
91
        # feeding the bytes in the http request to the smart_protocol_request,
106
106
    one. This will currently fail if the primary transport is not
107
107
    backed by regular disk files.
108
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
 
109
116
    def setUp(self):
110
117
        super(TestCaseWithWebserver, self).setUp()
111
118
        self.transport_readonly_server = http_server.HttpServer
112
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
 
113
126
 
114
127
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
115
128
    """A support class providing readonly urls on two servers that are http://.
117
130
    We set up two webservers to allows various tests involving
118
131
    proxies or redirections from one server to the other.
119
132
    """
 
133
 
120
134
    def setUp(self):
121
135
        super(TestCaseWithTwoWebservers, self).setUp()
122
136
        self.transport_secondary_server = http_server.HttpServer
127
141
 
128
142
        This is mostly a hook for daughter classes.
129
143
        """
130
 
        return self.transport_secondary_server()
 
144
        server = self.transport_secondary_server(
 
145
            protocol_version=self._protocol_version)
 
146
        server._url_protocol = self._url_protocol
 
147
        return server
131
148
 
132
149
    def get_secondary_server(self):
133
150
        """Get the server instance for the secondary transport."""
136
153
            self.start_server(self.__secondary_server)
137
154
        return self.__secondary_server
138
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
 
139
165
 
140
166
class ProxyServer(http_server.HttpServer):
141
167
    """A proxy test server for http transports."""
159
185
                # We do not send a body
160
186
                self.send_header('Content-Length', '0')
161
187
                self.end_headers()
162
 
                return False # The job is done
 
188
                return False  # The job is done
163
189
            else:
164
190
                # We leave the parent class serve the request
165
191
                pass
184
210
    def redirect_to(self, host, port):
185
211
        """Redirect all requests to a specific host:port"""
186
212
        self.redirections = [('(.*)',
187
 
                              r'http://%s:%s\1' % (host, port) ,
 
213
                              r'http://%s:%s\1' % (host, port),
188
214
                              301)]
189
215
 
190
216
    def is_redirected(self, path):
198
224
        code = None
199
225
        target = None
200
226
        for (rsource, rtarget, rcode) in self.redirections:
201
 
            target, match = re.subn(rsource, rtarget, path)
 
227
            target, match = re.subn(rsource, rtarget, path, count=1)
202
228
            if match:
203
229
                code = rcode
204
 
                break # The first match wins
 
230
                break  # The first match wins
205
231
            else:
206
232
                target = None
207
233
        return code, target
208
234
 
209
235
 
210
236
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()
 
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
    """
 
243
 
 
244
    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
231
277
 
232
278
 
233
279
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
243
289
    # - auth_header_recv: the header received containing auth
244
290
    # - auth_error_code: the error code to indicate auth required
245
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
 
246
305
    def do_GET(self):
247
306
        if self.authorized():
248
307
            return http_server.TestingHTTPRequestHandler.do_GET(self)
249
308
        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
 
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()
261
316
 
262
317
 
263
318
class BasicAuthRequestHandler(AuthRequestHandler):
272
327
        if auth_header:
273
328
            scheme, raw_auth = auth_header.split(' ', 1)
274
329
            if scheme.lower() == tcs.auth_scheme:
275
 
                user, password = raw_auth.decode('base64').split(':')
276
 
                return tcs.authorized(user, password)
 
330
                user, password = base64.b64decode(raw_auth).split(b':')
 
331
                return tcs.authorized(user.decode('ascii'),
 
332
                                      password.decode('ascii'))
277
333
 
278
334
        return False
279
335
 
302
358
            return False
303
359
        scheme, auth = auth_header.split(None, 1)
304
360
        if scheme.lower() == tcs.auth_scheme:
305
 
            auth_dict = urllib2.parse_keqv_list(urllib2.parse_http_list(auth))
 
361
            auth_dict = parse_keqv_list(parse_http_list(auth))
306
362
 
307
363
            return tcs.digest_authorized(auth_dict, self.command)
308
364
 
313
369
        header = 'Digest realm="%s", ' % tcs.auth_realm
314
370
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
315
371
                                                              'MD5')
316
 
        self.send_header(tcs.auth_header_sent,header)
 
372
        self.send_header(tcs.auth_header_sent, header)
317
373
 
318
374
 
319
375
class DigestAndBasicAuthRequestHandler(DigestAuthRequestHandler):
331
387
        header = 'Digest realm="%s", ' % tcs.auth_realm
332
388
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
333
389
                                                              'MD5')
334
 
        self.send_header(tcs.auth_header_sent,header)
 
390
        self.send_header(tcs.auth_header_sent, header)
335
391
 
336
392
 
337
393
class AuthServer(http_server.HttpServer):
349
405
    auth_header_sent = None
350
406
    auth_header_recv = None
351
407
    auth_error_code = None
352
 
    auth_realm = "Thou should not pass"
 
408
    auth_realm = u"Thou should not pass"
353
409
 
354
410
    def __init__(self, request_handler, auth_scheme,
355
411
                 protocol_version=None):
396
452
        if realm != self.auth_realm:
397
453
            return False
398
454
        user = auth['username']
399
 
        if not self.password_of.has_key(user):
 
455
        if user not in self.password_of:
400
456
            return False
401
 
        algorithm= auth['algorithm']
 
457
        algorithm = auth['algorithm']
402
458
        if algorithm != 'MD5':
403
459
            return False
404
460
        qop = auth['qop']
409
465
 
410
466
        # Recalculate the response_digest to compare with the one
411
467
        # 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))
 
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'))
417
476
 
418
477
        nonce_count = int(auth['nc'], 16)
419
478
 
503
562
        self.init_proxy_auth()
504
563
        # We really accept Digest only
505
564
        self.auth_scheme = 'digest'
506
 
 
507