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
17
17
from cStringIO import StringIO
20
from SimpleHTTPServer import SimpleHTTPRequestHandler
33
from bzrlib.smart import (
37
from bzrlib.tests import http_server
38
from bzrlib.transport import chroot
41
class HTTPServerWithSmarts(http_server.HttpServer):
28
from bzrlib.smart import protocol
29
from bzrlib.tests import TestCaseWithTransport
30
from bzrlib.tests.HttpServer import (
32
TestingHTTPRequestHandler,
34
from bzrlib.transport import (
39
class WallRequestHandler(TestingHTTPRequestHandler):
40
"""Whatever request comes in, close the connection"""
42
def handle_one_request(self):
43
"""Handle a single HTTP request, by abruptly closing the connection"""
44
self.close_connection = 1
47
class BadStatusRequestHandler(TestingHTTPRequestHandler):
48
"""Whatever request comes in, returns a bad status"""
50
def parse_request(self):
51
"""Fakes handling a single HTTP request, returns a bad status"""
52
ignored = TestingHTTPRequestHandler.parse_request(self)
54
self.send_response(0, "Bad status")
56
except socket.error, e:
57
# We don't want to pollute the test results with
58
# spurious server errors while test succeed. In our
59
# case, it may occur that the test has already read
60
# the 'Bad Status' and closed the socket while we are
61
# still trying to send some headers... So the test is
62
# ok, but if we raise the exception, the output is
63
# dirty. So we don't raise, but we close the
64
# connection, just to be safe :)
65
spurious = [errno.EPIPE,
69
if (len(e.args) > 0) and (e.args[0] in spurious):
70
self.close_connection = 1
77
class InvalidStatusRequestHandler(TestingHTTPRequestHandler):
78
"""Whatever request comes in, returns am invalid status"""
80
def parse_request(self):
81
"""Fakes handling a single HTTP request, returns a bad status"""
82
ignored = TestingHTTPRequestHandler.parse_request(self)
83
self.wfile.write("Invalid status line\r\n")
87
class BadProtocolRequestHandler(TestingHTTPRequestHandler):
88
"""Whatever request comes in, returns a bad protocol version"""
90
def parse_request(self):
91
"""Fakes handling a single HTTP request, returns a bad status"""
92
ignored = TestingHTTPRequestHandler.parse_request(self)
93
# Returns an invalid protocol version, but curl just
94
# ignores it and those cannot be tested.
95
self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
97
'Look at my protocol version'))
101
class ForbiddenRequestHandler(TestingHTTPRequestHandler):
102
"""Whatever request comes in, returns a 403 code"""
104
def parse_request(self):
105
"""Handle a single HTTP request, by replying we cannot handle it"""
106
ignored = TestingHTTPRequestHandler.parse_request(self)
111
class HTTPServerWithSmarts(HttpServer):
42
112
"""HTTPServerWithSmarts extends the HttpServer with POST methods that will
43
113
trigger a smart server to execute with a transport rooted at the rootdir of
47
def __init__(self, protocol_version=None):
48
http_server.HttpServer.__init__(self, SmartRequestHandler,
49
protocol_version=protocol_version)
52
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
53
"""Extend TestingHTTPRequestHandler to support smart client POSTs.
55
XXX: This duplicates a fair bit of the logic in bzrlib.transport.http.wsgi.
118
HttpServer.__init__(self, SmartRequestHandler)
121
class SmartRequestHandler(TestingHTTPRequestHandler):
122
"""Extend TestingHTTPRequestHandler to support smart client POSTs."""
58
124
def do_POST(self):
59
125
"""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()
65
t = transport.get_transport(chroot_server.get_url())
68
chroot_server.stop_server()
70
def do_POST_inner(self, chrooted_transport):
71
126
self.send_response(200)
72
127
self.send_header("Content-type", "application/octet-stream")
73
if not self.path.endswith('.bzr/smart'):
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'])
128
transport = get_transport(self.server.test_case_server._home_dir)
80
129
# TODO: We might like to support streaming responses. 1.0 allows no
81
130
# Content-length in this case, so for integrity we should perform our
82
131
# own chunking within the stream.
84
133
# the HTTP chunking as this will allow HTTP persistence safely, even if
85
134
# we have to stop early due to error, but we would also have to use the
86
135
# 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(
90
136
out_buffer = StringIO()
91
smart_protocol_request = protocol_factory(t, out_buffer.write, '/')
137
smart_protocol_request = protocol.SmartServerRequestProtocolOne(
138
transport, out_buffer.write)
139
# if this fails, we should return 400 bad request, but failure is
140
# failure for now - RBC 20060919
141
data_length = int(self.headers['Content-Length'])
92
142
# Perhaps there should be a SmartServerHTTPMedium that takes care of
93
143
# feeding the bytes in the http request to the smart_protocol_request,
94
144
# 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.")
145
smart_protocol_request.accept_bytes(self.rfile.read(data_length))
146
assert smart_protocol_request.next_read_size() == 0, (
147
"not finished reading, but all data sent to protocol.")
99
148
self.send_header("Content-Length", str(len(out_buffer.getvalue())))
100
149
self.end_headers()
101
150
self.wfile.write(out_buffer.getvalue())
104
class TestCaseWithWebserver(tests.TestCaseWithTransport):
153
class LimitedRangeRequestHandler(TestingHTTPRequestHandler):
154
"""Errors out when range specifiers exceed the limit"""
156
def get_multiple_ranges(self, file, file_size, ranges):
157
"""Refuses the multiple ranges request"""
158
tcs = self.server.test_case_server
159
if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
161
# Emulate apache behavior
162
self.send_error(400, "Bad Request")
164
return TestingHTTPRequestHandler.get_multiple_ranges(self, file,
168
tcs = self.server.test_case_server
169
tcs.GET_request_nb += 1
170
return TestingHTTPRequestHandler.do_GET(self)
173
class LimitedRangeHTTPServer(HttpServer):
174
"""An HttpServer erroring out on requests with too much range specifiers"""
176
def __init__(self, request_handler=LimitedRangeRequestHandler,
178
HttpServer.__init__(self, request_handler)
179
self.range_limit = range_limit
180
self.GET_request_nb = 0
183
class SingleRangeRequestHandler(TestingHTTPRequestHandler):
184
"""Always reply to range request as if they were single.
186
Don't be explicit about it, just to annoy the clients.
189
def get_multiple_ranges(self, file, file_size, ranges):
190
"""Answer as if it was a single range request and ignores the rest"""
191
(start, end) = ranges[0]
192
return self.get_single_range(file, file_size, start, end)
195
class SingleOnlyRangeRequestHandler(TestingHTTPRequestHandler):
196
"""Only reply to simple range requests, errors out on multiple"""
198
def get_multiple_ranges(self, file, file_size, ranges):
199
"""Refuses the multiple ranges request"""
202
self.send_error(416, "Requested range not satisfiable")
204
(start, end) = ranges[0]
205
return self.get_single_range(file, file_size, start, end)
208
class NoRangeRequestHandler(TestingHTTPRequestHandler):
209
"""Ignore range requests without notice"""
211
# Just bypass the range handling done by TestingHTTPRequestHandler
212
do_GET = SimpleHTTPRequestHandler.do_GET
215
class TestCaseWithWebserver(TestCaseWithTransport):
105
216
"""A support class that provides readonly urls that are http://.
107
218
This is done by forcing the readonly server to be an http
108
219
one. This will currently fail if the primary transport is not
109
220
backed by regular disk files.
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'
119
223
super(TestCaseWithWebserver, self).setUp()
120
self.transport_readonly_server = http_server.HttpServer
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
224
self.transport_readonly_server = HttpServer
129
227
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
143
241
This is mostly a hook for daughter classes.
145
server = self.transport_secondary_server(
146
protocol_version=self._protocol_version)
147
server._url_protocol = self._url_protocol
243
return self.transport_secondary_server()
150
245
def get_secondary_server(self):
151
246
"""Get the server instance for the secondary transport."""
152
247
if self.__secondary_server is None:
153
248
self.__secondary_server = self.create_transport_secondary_server()
154
self.start_server(self.__secondary_server)
249
self.__secondary_server.setUp()
250
self.addCleanup(self.__secondary_server.tearDown)
155
251
return self.__secondary_server
157
def get_secondary_url(self, relpath=None):
158
base = self.get_secondary_server().get_url()
159
return self._adjust_url(base, relpath)
161
def get_secondary_transport(self, relpath=None):
162
t = transport.get_transport(self.get_secondary_url(relpath))
163
self.assertTrue(t.is_readonly())
167
class ProxyServer(http_server.HttpServer):
254
class ProxyServer(HttpServer):
168
255
"""A proxy test server for http transports."""
170
257
proxy_requests = True
173
class RedirectRequestHandler(http_server.TestingHTTPRequestHandler):
260
class RedirectRequestHandler(TestingHTTPRequestHandler):
174
261
"""Redirect all request to the specified server"""
176
263
def parse_request(self):
177
264
"""Redirect a single HTTP request to another host"""
178
valid = http_server.TestingHTTPRequestHandler.parse_request(self)
265
valid = TestingHTTPRequestHandler.parse_request(self)
180
267
tcs = self.server.test_case_server
181
268
code, target = tcs.is_redirected(self.path)
242
325
The 'old' server is redirected to the 'new' server.
328
def create_transport_secondary_server(self):
329
"""Create the secondary server redirecting to the primary server"""
330
new = self.get_readonly_server()
331
redirecting = HTTPServerRedirecting()
332
redirecting.redirect_to(new.host, new.port)
246
336
super(TestCaseWithRedirectedWebserver, self).setUp()
247
337
# The redirections will point to the new server
248
338
self.new_server = self.get_readonly_server()
249
# The requests to the old server will be redirected to the new server
339
# The requests to the old server will be redirected
250
340
self.old_server = self.get_secondary_server()
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
261
def get_old_url(self, relpath=None):
262
base = self.old_server.get_url()
263
return self._adjust_url(base, relpath)
265
def get_old_transport(self, relpath=None):
266
t = transport.get_transport(self.get_old_url(relpath))
267
self.assertTrue(t.is_readonly())
270
def get_new_url(self, relpath=None):
271
base = self.new_server.get_url()
272
return self._adjust_url(base, relpath)
274
def get_new_transport(self, relpath=None):
275
t = transport.get_transport(self.get_new_url(relpath))
276
self.assertTrue(t.is_readonly())
280
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
343
class AuthRequestHandler(TestingHTTPRequestHandler):
281
344
"""Requires an authentication to process requests.
283
346
This is intended to be used with a server that always and
363
426
self.send_header(tcs.auth_header_sent,header)
366
class DigestAndBasicAuthRequestHandler(DigestAuthRequestHandler):
367
"""Implements a digest and basic authentication of a request.
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
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,
381
self.send_header(tcs.auth_header_sent,header)
384
class AuthServer(http_server.HttpServer):
429
class AuthServer(HttpServer):
385
430
"""Extends HttpServer with a dictionary of passwords.
387
432
This is used as a base class for various schemes which should
495
535
class HTTPBasicAuthServer(HTTPAuthServer):
496
536
"""An HTTP server requiring basic authentication"""
498
def __init__(self, protocol_version=None):
499
HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
500
protocol_version=protocol_version)
539
HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
501
540
self.init_http_auth()
504
543
class HTTPDigestAuthServer(DigestAuthServer, HTTPAuthServer):
505
544
"""An HTTP server requiring digest authentication"""
507
def __init__(self, protocol_version=None):
508
DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
509
protocol_version=protocol_version)
510
self.init_http_auth()
513
class HTTPBasicAndDigestAuthServer(DigestAuthServer, HTTPAuthServer):
514
"""An HTTP server requiring basic or digest authentication"""
516
def __init__(self, protocol_version=None):
517
DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
519
protocol_version=protocol_version)
520
self.init_http_auth()
521
# We really accept Digest only
522
self.auth_scheme = 'digest'
547
DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
548
self.init_http_auth()
525
551
class ProxyBasicAuthServer(ProxyAuthServer):
526
552
"""A proxy server requiring basic authentication"""
528
def __init__(self, protocol_version=None):
529
ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
530
protocol_version=protocol_version)
555
ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
531
556
self.init_proxy_auth()
534
559
class ProxyDigestAuthServer(DigestAuthServer, ProxyAuthServer):
535
560
"""A proxy server requiring basic authentication"""
537
def __init__(self, protocol_version=None):
538
ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
539
protocol_version=protocol_version)
540
self.init_proxy_auth()
543
class ProxyBasicAndDigestAuthServer(DigestAuthServer, ProxyAuthServer):
544
"""An proxy server requiring basic or digest authentication"""
546
def __init__(self, protocol_version=None):
547
DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
549
protocol_version=protocol_version)
550
self.init_proxy_auth()
551
# We really accept Digest only
552
self.auth_scheme = 'digest'
563
ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
564
self.init_proxy_auth()