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
27
29
from bzrlib import (
32
from bzrlib.smart import medium, protocol
33
from bzrlib.smart import protocol
33
34
from bzrlib.tests import http_server
34
from bzrlib.transport import (
40
37
class HTTPServerWithSmarts(http_server.HttpServer):
51
48
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.
49
"""Extend TestingHTTPRequestHandler to support smart client POSTs."""
58
52
"""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
53
self.send_response(200)
70
54
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'])
55
t = transport.get_transport(self.server.test_case_server._home_dir)
78
56
# TODO: We might like to support streaming responses. 1.0 allows no
79
57
# Content-length in this case, so for integrity we should perform our
80
58
# own chunking within the stream.
82
60
# the HTTP chunking as this will allow HTTP persistence safely, even if
83
61
# we have to stop early due to error, but we would also have to use the
84
62
# 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
63
out_buffer = StringIO()
89
smart_protocol_request = protocol_factory(t, out_buffer.write, '/')
64
smart_protocol_request = protocol.SmartServerRequestProtocolOne(
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'])
90
69
# Perhaps there should be a SmartServerHTTPMedium that takes care of
91
70
# feeding the bytes in the http request to the smart_protocol_request,
92
71
# 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.")
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.")
97
75
self.send_header("Content-Length", str(len(out_buffer.getvalue())))
99
77
self.wfile.write(out_buffer.getvalue())
133
111
"""Get the server instance for the secondary transport."""
134
112
if self.__secondary_server is None:
135
113
self.__secondary_server = self.create_transport_secondary_server()
136
self.start_server(self.__secondary_server)
114
self.__secondary_server.setUp()
115
self.addCleanup(self.__secondary_server.tearDown)
137
116
return self.__secondary_server
316
297
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
300
class AuthServer(http_server.HttpServer):
338
301
"""Extends HttpServer with a dictionary of passwords.
412
375
A1 = '%s:%s:%s' % (user, realm, password)
413
376
A2 = '%s:%s' % (command, auth['uri'])
415
H = lambda x: osutils.md5(x).hexdigest()
378
H = lambda x: md5.new(x).hexdigest()
416
379
KD = lambda secret, data: H("%s:%s" % (secret, data))
418
381
nonce_count = int(auth['nc'], 16)
463
425
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'
478
428
class ProxyBasicAuthServer(ProxyAuthServer):
479
429
"""A proxy server requiring basic authentication"""
493
443
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'