1
# Copyright (C) 2006 Canonical Ltd
1
# Copyright (C) 2006-2010 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
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
"""WSGI application for bzr HTTP smart server.
23
23
from cStringIO import StringIO
25
from bzrlib.smart import protocol
25
from bzrlib.smart import protocol, medium
26
26
from bzrlib.transport import chroot, get_transport
27
27
from bzrlib.urlutils import local_path_to_url
30
def make_app(root, prefix, path_var, readonly=True):
30
def make_app(root, prefix, path_var='REQUEST_URI', readonly=True,
31
load_plugins=True, enable_logging=True):
31
32
"""Convenience function to construct a WSGI bzr smart server.
33
34
:param root: a local path that requests will be relative to.
34
35
:param prefix: See RelpathSetter.
35
36
:param path_var: See RelpathSetter.
39
40
base_transport = get_transport('readonly+' + local_url)
41
42
base_transport = get_transport(local_url)
42
app = SmartWSGIApp(base_transport)
43
app = RelpathSetter(app, prefix, path_var)
44
from bzrlib.plugin import load_plugins
48
bzrlib.trace.enable_default_logging()
49
app = SmartWSGIApp(base_transport, prefix)
50
app = RelpathSetter(app, '', path_var)
47
54
class RelpathSetter(object):
48
55
"""WSGI middleware to set 'bzrlib.relpath' in the environ.
50
57
Different servers can invoke a SmartWSGIApp in different ways. This
51
58
middleware allows an adminstrator to configure how to the SmartWSGIApp will
52
59
determine what path it should be serving for a given request for many common
58
65
prefix="/some/prefix/" and path_var="REQUEST_URI" will set that request's
59
66
'bzrlib.relpath' variable to "repo/branch".
62
69
def __init__(self, app, prefix='', path_var='REQUEST_URI'):
85
92
class SmartWSGIApp(object):
86
93
"""A WSGI application for the bzr smart server."""
88
def __init__(self, backing_transport):
95
def __init__(self, backing_transport, root_client_path='/'):
91
98
:param backing_transport: a transport. Requests will be processed
92
99
relative to this transport.
100
:param root_client_path: the client path that maps to the root of
101
backing_transport. This is used to interpret relpaths received from
94
# Use a ChrootTransportDecorator so that this web application won't
104
# Use a ChrootServer so that this web application won't
95
105
# accidentally let people access locations they shouldn't.
96
106
# e.g. consider a smart server request for "get /etc/passwd" or
98
108
self.chroot_server = chroot.ChrootServer(backing_transport)
99
self.chroot_server.setUp()
109
self.chroot_server.start_server()
100
110
self.backing_transport = get_transport(self.chroot_server.get_url())
111
self.root_client_path = root_client_path
101
112
# While the chroot server can technically be torn down at this point,
102
# as all it does is remove the scheme registration from transport's
103
# protocol dictionary, we don't *just in case* there are parts of
113
# as all it does is remove the scheme registration from transport's
114
# protocol dictionary, we don't *just in case* there are parts of
104
115
# bzrlib that will invoke 'get_transport' on urls rather than cloning
105
116
# around the existing transport.
106
#self.chroot_server.tearDown()
117
#self.chroot_server.stop_server()
108
119
def __call__(self, environ, start_response):
109
120
"""WSGI application callable."""
114
125
relpath = environ['bzrlib.relpath']
115
transport = self.backing_transport.clone(relpath)
127
if not relpath.startswith('/'):
128
relpath = '/' + relpath
129
if not relpath.endswith('/'):
132
# Compare the HTTP path (relpath) and root_client_path, and calculate
133
# new relpath and root_client_path accordingly, to be used to build the
135
if relpath.startswith(self.root_client_path):
136
# The relpath traverses all of the mandatory root client path.
137
# Remove the root_client_path from the relpath, and set
138
# adjusted_tcp to None to tell the request handler that no further
139
# path translation is required.
141
adjusted_relpath = relpath[len(self.root_client_path):]
142
elif self.root_client_path.startswith(relpath):
143
# The relpath traverses some of the mandatory root client path.
144
# Subtract the relpath from the root_client_path, and set the
146
adjusted_rcp = '/' + self.root_client_path[len(relpath):]
147
adjusted_relpath = '.'
149
adjusted_rcp = self.root_client_path
150
adjusted_relpath = relpath
152
if adjusted_relpath.startswith('/'):
153
adjusted_relpath = adjusted_relpath[1:]
154
if adjusted_relpath.startswith('/'):
155
raise AssertionError(adjusted_relpath)
157
transport = self.backing_transport.clone(adjusted_relpath)
116
158
out_buffer = StringIO()
117
159
request_data_length = int(environ['CONTENT_LENGTH'])
118
160
request_data_bytes = environ['wsgi.input'].read(request_data_length)
119
161
smart_protocol_request = self.make_request(
120
transport, out_buffer.write, request_data_bytes)
162
transport, out_buffer.write, request_data_bytes, adjusted_rcp)
121
163
if smart_protocol_request.next_read_size() != 0:
122
164
# The request appears to be incomplete, or perhaps it's just a
123
165
# newer version we don't understand. Regardless, all we can do
131
173
start_response('200 OK', headers)
132
174
return [response_data]
134
def make_request(self, transport, write_func, request_bytes):
135
# XXX: This duplicates the logic in
136
# SmartServerStreamMedium._build_protocol.
137
if request_bytes.startswith(protocol.REQUEST_VERSION_TWO):
138
protocol_class = protocol.SmartServerRequestProtocolTwo
139
request_bytes = request_bytes[len(protocol.REQUEST_VERSION_TWO):]
141
protocol_class = protocol.SmartServerRequestProtocolOne
142
server_protocol = protocol_class(transport, write_func)
143
server_protocol.accept_bytes(request_bytes)
176
def make_request(self, transport, write_func, request_bytes, rcp):
177
protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
179
server_protocol = protocol_factory(
180
transport, write_func, rcp, self.backing_transport)
181
server_protocol.accept_bytes(unused_bytes)
144
182
return server_protocol