1
# Copyright (C) 2005-2010 Canonical Ltd
1
# Copyright (C) 2005-2011 Canonical Ltd
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
17
from cStringIO import StringIO
20
from urllib.request import (
24
except ImportError: # python < 3
32
from bzrlib.smart import medium, protocol
33
from bzrlib.tests import http_server
34
from bzrlib.transport import (
37
from ..sixish import (
40
from ..bzr.smart import (
43
from . import http_server
44
from ..transport import chroot
40
47
class HTTPServerWithSmarts(http_server.HttpServer):
51
58
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
52
59
"""Extend TestingHTTPRequestHandler to support smart client POSTs.
54
XXX: This duplicates a fair bit of the logic in bzrlib.transport.http.wsgi.
61
XXX: This duplicates a fair bit of the logic in breezy.transport.http.wsgi.
58
65
"""Hand the request off to a smart server instance."""
59
backing = get_transport(self.server.test_case_server._home_dir)
66
backing = transport.get_transport_from_path(
67
self.server.test_case_server._home_dir)
60
68
chroot_server = chroot.ChrootServer(backing)
61
69
chroot_server.start_server()
63
t = get_transport(chroot_server.get_url())
71
t = transport.get_transport_from_url(chroot_server.get_url())
64
72
self.do_POST_inner(t)
66
74
chroot_server.stop_server()
85
93
request_bytes = self.rfile.read(data_length)
86
94
protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
88
out_buffer = StringIO()
96
out_buffer = BytesIO()
89
97
smart_protocol_request = protocol_factory(t, out_buffer.write, '/')
90
98
# Perhaps there should be a SmartServerHTTPMedium that takes care of
91
99
# feeding the bytes in the http request to the smart_protocol_request,
106
114
one. This will currently fail if the primary transport is not
107
115
backed by regular disk files.
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'
110
125
super(TestCaseWithWebserver, self).setUp()
111
126
self.transport_readonly_server = http_server.HttpServer
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
114
135
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
115
136
"""A support class providing readonly urls on two servers that are http://.
128
149
This is mostly a hook for daughter classes.
130
return self.transport_secondary_server()
151
server = self.transport_secondary_server(
152
protocol_version=self._protocol_version)
153
server._url_protocol = self._url_protocol
132
156
def get_secondary_server(self):
133
157
"""Get the server instance for the secondary transport."""
136
160
self.start_server(self.__secondary_server)
137
161
return self.__secondary_server
163
def get_secondary_url(self, relpath=None):
164
base = self.get_secondary_server().get_url()
165
return self._adjust_url(base, relpath)
167
def get_secondary_transport(self, relpath=None):
168
t = transport.get_transport_from_url(self.get_secondary_url(relpath))
169
self.assertTrue(t.is_readonly())
140
173
class ProxyServer(http_server.HttpServer):
141
174
"""A proxy test server for http transports."""
184
217
def redirect_to(self, host, port):
185
218
"""Redirect all requests to a specific host:port"""
186
219
self.redirections = [('(.*)',
187
r'http://%s:%s\1' % (host, port) ,
220
r'http://%s:%s\1' % (host, port),
190
223
def is_redirected(self, path):
215
248
The 'old' server is redirected to the 'new' server.
252
super(TestCaseWithRedirectedWebserver, self).setUp()
253
# The redirections will point to the new server
254
self.new_server = self.get_readonly_server()
255
# The requests to the old server will be redirected to the new server
256
self.old_server = self.get_secondary_server()
218
258
def create_transport_secondary_server(self):
219
259
"""Create the secondary server redirecting to the primary server"""
220
260
new = self.get_readonly_server()
221
redirecting = HTTPServerRedirecting()
261
redirecting = HTTPServerRedirecting(
262
protocol_version=self._protocol_version)
222
263
redirecting.redirect_to(new.host, new.port)
264
redirecting._url_protocol = self._url_protocol
223
265
return redirecting
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()
267
def get_old_url(self, relpath=None):
268
base = self.old_server.get_url()
269
return self._adjust_url(base, relpath)
271
def get_old_transport(self, relpath=None):
272
t = transport.get_transport_from_url(self.get_old_url(relpath))
273
self.assertTrue(t.is_readonly())
276
def get_new_url(self, relpath=None):
277
base = self.new_server.get_url()
278
return self._adjust_url(base, relpath)
280
def get_new_transport(self, relpath=None):
281
t = transport.get_transport_from_url(self.get_new_url(relpath))
282
self.assertTrue(t.is_readonly())
233
286
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
243
296
# - auth_header_recv: the header received containing auth
244
297
# - auth_error_code: the error code to indicate auth required
299
def _require_authentication(self):
300
# Note that we must update test_case_server *before*
301
# sending the error or the client may try to read it
302
# before we have sent the whole error back.
303
tcs = self.server.test_case_server
304
tcs.auth_required_errors += 1
305
self.send_response(tcs.auth_error_code)
306
self.send_header_auth_reqed()
307
# We do not send a body
308
self.send_header('Content-Length', '0')
246
312
def do_GET(self):
247
313
if self.authorized():
248
314
return http_server.TestingHTTPRequestHandler.do_GET(self)
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')
316
return self._require_authentication()
319
if self.authorized():
320
return http_server.TestingHTTPRequestHandler.do_HEAD(self)
322
return self._require_authentication()
263
325
class BasicAuthRequestHandler(AuthRequestHandler):
273
335
scheme, raw_auth = auth_header.split(' ', 1)
274
336
if scheme.lower() == tcs.auth_scheme:
275
user, password = raw_auth.decode('base64').split(':')
276
return tcs.authorized(user, password)
337
user, password = base64.b64decode(raw_auth).split(b':')
338
return tcs.authorized(user.decode('ascii'),
339
password.decode('ascii'))
303
366
scheme, auth = auth_header.split(None, 1)
304
367
if scheme.lower() == tcs.auth_scheme:
305
auth_dict = urllib2.parse_keqv_list(urllib2.parse_http_list(auth))
368
auth_dict = parse_keqv_list(parse_http_list(auth))
307
370
return tcs.digest_authorized(auth_dict, self.command)
313
376
header = 'Digest realm="%s", ' % tcs.auth_realm
314
377
header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
316
self.send_header(tcs.auth_header_sent,header)
379
self.send_header(tcs.auth_header_sent, header)
319
382
class DigestAndBasicAuthRequestHandler(DigestAuthRequestHandler):
331
394
header = 'Digest realm="%s", ' % tcs.auth_realm
332
395
header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
334
self.send_header(tcs.auth_header_sent,header)
397
self.send_header(tcs.auth_header_sent, header)
337
400
class AuthServer(http_server.HttpServer):
349
412
auth_header_sent = None
350
413
auth_header_recv = None
351
414
auth_error_code = None
352
auth_realm = "Thou should not pass"
415
auth_realm = u"Thou should not pass"
354
417
def __init__(self, request_handler, auth_scheme,
355
418
protocol_version=None):
410
473
# Recalculate the response_digest to compare with the one
411
474
# sent by the client
412
A1 = '%s:%s:%s' % (user, realm, password)
413
A2 = '%s:%s' % (command, auth['uri'])
475
A1 = ('%s:%s:%s' % (user, realm, password)).encode('utf-8')
476
A2 = ('%s:%s' % (command, auth['uri'])).encode('utf-8')
415
478
H = lambda x: osutils.md5(x).hexdigest()
416
KD = lambda secret, data: H("%s:%s" % (secret, data))
479
KD = lambda secret, data: H(("%s:%s" % (secret, data)).encode('utf-8'))
418
481
nonce_count = int(auth['nc'], 16)