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
19
from SimpleHTTPServer import SimpleHTTPRequestHandler
26
from bzrlib.osutils import md5
27
from bzrlib.smart import protocol
28
from bzrlib.tests import TestCaseWithTransport
29
from bzrlib.tests.http_server import (
31
TestingHTTPRequestHandler,
32
from bzrlib.smart import medium, protocol
33
from bzrlib.tests import http_server
34
33
from bzrlib.transport import (
40
class HTTPServerWithSmarts(http_server.HttpServer):
38
class WallRequestHandler(TestingHTTPRequestHandler):
39
"""Whatever request comes in, close the connection"""
41
def handle_one_request(self):
42
"""Handle a single HTTP request, by abruptly closing the connection"""
43
self.close_connection = 1
46
class BadStatusRequestHandler(TestingHTTPRequestHandler):
47
"""Whatever request comes in, returns a bad status"""
49
def parse_request(self):
50
"""Fakes handling a single HTTP request, returns a bad status"""
51
ignored = TestingHTTPRequestHandler.parse_request(self)
53
self.send_response(0, "Bad status")
55
except socket.error, e:
56
# We don't want to pollute the test results with
57
# spurious server errors while test succeed. In our
58
# case, it may occur that the test has already read
59
# the 'Bad Status' and closed the socket while we are
60
# still trying to send some headers... So the test is
61
# ok, but if we raise the exception, the output is
62
# dirty. So we don't raise, but we close the
63
# connection, just to be safe :)
64
spurious = [errno.EPIPE,
68
if (len(e.args) > 0) and (e.args[0] in spurious):
69
self.close_connection = 1
76
class InvalidStatusRequestHandler(TestingHTTPRequestHandler):
77
"""Whatever request comes in, returns am invalid status"""
79
def parse_request(self):
80
"""Fakes handling a single HTTP request, returns a bad status"""
81
ignored = TestingHTTPRequestHandler.parse_request(self)
82
self.wfile.write("Invalid status line\r\n")
86
class BadProtocolRequestHandler(TestingHTTPRequestHandler):
87
"""Whatever request comes in, returns a bad protocol version"""
89
def parse_request(self):
90
"""Fakes handling a single HTTP request, returns a bad status"""
91
ignored = TestingHTTPRequestHandler.parse_request(self)
92
# Returns an invalid protocol version, but curl just
93
# ignores it and those cannot be tested.
94
self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
96
'Look at my protocol version'))
100
class ForbiddenRequestHandler(TestingHTTPRequestHandler):
101
"""Whatever request comes in, returns a 403 code"""
103
def parse_request(self):
104
"""Handle a single HTTP request, by replying we cannot handle it"""
105
ignored = TestingHTTPRequestHandler.parse_request(self)
110
class HTTPServerWithSmarts(HttpServer):
41
111
"""HTTPServerWithSmarts extends the HttpServer with POST methods that will
42
112
trigger a smart server to execute with a transport rooted at the rootdir of
46
def __init__(self, protocol_version=None):
47
http_server.HttpServer.__init__(self, SmartRequestHandler,
48
protocol_version=protocol_version)
51
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
52
"""Extend TestingHTTPRequestHandler to support smart client POSTs.
54
XXX: This duplicates a fair bit of the logic in bzrlib.transport.http.wsgi.
117
HttpServer.__init__(self, SmartRequestHandler)
120
class SmartRequestHandler(TestingHTTPRequestHandler):
121
"""Extend TestingHTTPRequestHandler to support smart client POSTs."""
57
123
def do_POST(self):
58
124
"""Hand the request off to a smart server instance."""
59
backing = get_transport(self.server.test_case_server._home_dir)
60
chroot_server = chroot.ChrootServer(backing)
61
chroot_server.start_server()
63
t = get_transport(chroot_server.get_url())
66
chroot_server.stop_server()
68
def do_POST_inner(self, chrooted_transport):
69
125
self.send_response(200)
70
126
self.send_header("Content-type", "application/octet-stream")
71
if not self.path.endswith('.bzr/smart'):
73
'POST to path not ending in .bzr/smart: %r' % (self.path,))
74
t = chrooted_transport.clone(self.path[:-len('.bzr/smart')])
75
# if this fails, we should return 400 bad request, but failure is
76
# failure for now - RBC 20060919
77
data_length = int(self.headers['Content-Length'])
127
transport = get_transport(self.server.test_case_server._home_dir)
78
128
# TODO: We might like to support streaming responses. 1.0 allows no
79
129
# Content-length in this case, so for integrity we should perform our
80
130
# own chunking within the stream.
82
132
# the HTTP chunking as this will allow HTTP persistence safely, even if
83
133
# we have to stop early due to error, but we would also have to use the
84
134
# HTTP trailer facility which may not be widely available.
85
request_bytes = self.rfile.read(data_length)
86
protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
88
135
out_buffer = StringIO()
89
smart_protocol_request = protocol_factory(t, out_buffer.write, '/')
136
smart_protocol_request = protocol.SmartServerRequestProtocolOne(
137
transport, out_buffer.write)
138
# if this fails, we should return 400 bad request, but failure is
139
# failure for now - RBC 20060919
140
data_length = int(self.headers['Content-Length'])
90
141
# Perhaps there should be a SmartServerHTTPMedium that takes care of
91
142
# feeding the bytes in the http request to the smart_protocol_request,
92
143
# but for now it's simpler to just feed the bytes directly.
93
smart_protocol_request.accept_bytes(unused_bytes)
94
if not (smart_protocol_request.next_read_size() == 0):
95
raise errors.SmartProtocolError(
96
"not finished reading, but all data sent to protocol.")
144
smart_protocol_request.accept_bytes(self.rfile.read(data_length))
145
assert smart_protocol_request.next_read_size() == 0, (
146
"not finished reading, but all data sent to protocol.")
97
147
self.send_header("Content-Length", str(len(out_buffer.getvalue())))
98
148
self.end_headers()
99
149
self.wfile.write(out_buffer.getvalue())
102
class TestCaseWithWebserver(tests.TestCaseWithTransport):
152
class LimitedRangeRequestHandler(TestingHTTPRequestHandler):
153
"""Errors out when range specifiers exceed the limit"""
155
def get_multiple_ranges(self, file, file_size, ranges):
156
"""Refuses the multiple ranges request"""
157
tcs = self.server.test_case_server
158
if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
160
# Emulate apache behavior
161
self.send_error(400, "Bad Request")
163
return TestingHTTPRequestHandler.get_multiple_ranges(self, file,
167
tcs = self.server.test_case_server
168
tcs.GET_request_nb += 1
169
return TestingHTTPRequestHandler.do_GET(self)
172
class LimitedRangeHTTPServer(HttpServer):
173
"""An HttpServer erroring out on requests with too much range specifiers"""
175
def __init__(self, request_handler=LimitedRangeRequestHandler,
177
HttpServer.__init__(self, request_handler)
178
self.range_limit = range_limit
179
self.GET_request_nb = 0
182
class SingleRangeRequestHandler(TestingHTTPRequestHandler):
183
"""Always reply to range request as if they were single.
185
Don't be explicit about it, just to annoy the clients.
188
def get_multiple_ranges(self, file, file_size, ranges):
189
"""Answer as if it was a single range request and ignores the rest"""
190
(start, end) = ranges[0]
191
return self.get_single_range(file, file_size, start, end)
194
class SingleOnlyRangeRequestHandler(TestingHTTPRequestHandler):
195
"""Only reply to simple range requests, errors out on multiple"""
197
def get_multiple_ranges(self, file, file_size, ranges):
198
"""Refuses the multiple ranges request"""
201
self.send_error(416, "Requested range not satisfiable")
203
(start, end) = ranges[0]
204
return self.get_single_range(file, file_size, start, end)
207
class NoRangeRequestHandler(TestingHTTPRequestHandler):
208
"""Ignore range requests without notice"""
210
# Just bypass the range handling done by TestingHTTPRequestHandler
211
do_GET = SimpleHTTPRequestHandler.do_GET
214
class TestCaseWithWebserver(TestCaseWithTransport):
103
215
"""A support class that provides readonly urls that are http://.
105
217
This is done by forcing the readonly server to be an http
133
245
"""Get the server instance for the secondary transport."""
134
246
if self.__secondary_server is None:
135
247
self.__secondary_server = self.create_transport_secondary_server()
136
self.start_server(self.__secondary_server)
248
self.__secondary_server.setUp()
249
self.addCleanup(self.__secondary_server.tearDown)
137
250
return self.__secondary_server
140
class ProxyServer(http_server.HttpServer):
253
class ProxyServer(HttpServer):
141
254
"""A proxy test server for http transports."""
143
256
proxy_requests = True
146
class RedirectRequestHandler(http_server.TestingHTTPRequestHandler):
259
class RedirectRequestHandler(TestingHTTPRequestHandler):
147
260
"""Redirect all request to the specified server"""
149
262
def parse_request(self):
150
263
"""Redirect a single HTTP request to another host"""
151
valid = http_server.TestingHTTPRequestHandler.parse_request(self)
264
valid = TestingHTTPRequestHandler.parse_request(self)
153
266
tcs = self.server.test_case_server
154
267
code, target = tcs.is_redirected(self.path)
316
425
self.send_header(tcs.auth_header_sent,header)
319
class DigestAndBasicAuthRequestHandler(DigestAuthRequestHandler):
320
"""Implements a digest and basic authentication of a request.
322
I.e. the server proposes both schemes and the client should choose the best
323
one it can handle, which, in that case, should be digest, the only scheme
327
def send_header_auth_reqed(self):
328
tcs = self.server.test_case_server
329
self.send_header(tcs.auth_header_sent,
330
'Basic realm="%s"' % tcs.auth_realm)
331
header = 'Digest realm="%s", ' % tcs.auth_realm
332
header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
334
self.send_header(tcs.auth_header_sent,header)
337
class AuthServer(http_server.HttpServer):
428
class AuthServer(HttpServer):
338
429
"""Extends HttpServer with a dictionary of passwords.
340
431
This is used as a base class for various schemes which should
448
534
class HTTPBasicAuthServer(HTTPAuthServer):
449
535
"""An HTTP server requiring basic authentication"""
451
def __init__(self, protocol_version=None):
452
HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
453
protocol_version=protocol_version)
538
HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
454
539
self.init_http_auth()
457
542
class HTTPDigestAuthServer(DigestAuthServer, HTTPAuthServer):
458
543
"""An HTTP server requiring digest authentication"""
460
def __init__(self, protocol_version=None):
461
DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
462
protocol_version=protocol_version)
463
self.init_http_auth()
466
class HTTPBasicAndDigestAuthServer(DigestAuthServer, HTTPAuthServer):
467
"""An HTTP server requiring basic or digest authentication"""
469
def __init__(self, protocol_version=None):
470
DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
472
protocol_version=protocol_version)
473
self.init_http_auth()
474
# We really accept Digest only
475
self.auth_scheme = 'digest'
546
DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
547
self.init_http_auth()
478
550
class ProxyBasicAuthServer(ProxyAuthServer):
479
551
"""A proxy server requiring basic authentication"""
481
def __init__(self, protocol_version=None):
482
ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
483
protocol_version=protocol_version)
554
ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
484
555
self.init_proxy_auth()
487
558
class ProxyDigestAuthServer(DigestAuthServer, ProxyAuthServer):
488
559
"""A proxy server requiring basic authentication"""
490
def __init__(self, protocol_version=None):
491
ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
492
protocol_version=protocol_version)
493
self.init_proxy_auth()
496
class ProxyBasicAndDigestAuthServer(DigestAuthServer, ProxyAuthServer):
497
"""An proxy server requiring basic or digest authentication"""
499
def __init__(self, protocol_version=None):
500
DigestAuthServer.__init__(self, DigestAndBasicAuthRequestHandler,
502
protocol_version=protocol_version)
503
self.init_proxy_auth()
504
# We really accept Digest only
505
self.auth_scheme = 'digest'
562
ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
563
self.init_proxy_auth()