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
"""WSGI application for bzr HTTP smart server.
23
23
from cStringIO import StringIO
25
from bzrlib.smart import protocol, medium
25
from bzrlib.smart import protocol
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='REQUEST_URI', readonly=True,
31
load_plugins=True, enable_logging=True):
30
def make_app(root, prefix, path_var, readonly=True):
32
31
"""Convenience function to construct a WSGI bzr smart server.
34
33
:param root: a local path that requests will be relative to.
35
34
:param prefix: See RelpathSetter.
36
35
:param path_var: See RelpathSetter.
40
39
base_transport = get_transport('readonly+' + local_url)
42
41
base_transport = get_transport(local_url)
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)
42
app = SmartWSGIApp(base_transport)
43
app = RelpathSetter(app, prefix, path_var)
54
47
class RelpathSetter(object):
55
48
"""WSGI middleware to set 'bzrlib.relpath' in the environ.
57
50
Different servers can invoke a SmartWSGIApp in different ways. This
58
51
middleware allows an adminstrator to configure how to the SmartWSGIApp will
59
52
determine what path it should be serving for a given request for many common
65
58
prefix="/some/prefix/" and path_var="REQUEST_URI" will set that request's
66
59
'bzrlib.relpath' variable to "repo/branch".
69
62
def __init__(self, app, prefix='', path_var='REQUEST_URI'):
92
85
class SmartWSGIApp(object):
93
86
"""A WSGI application for the bzr smart server."""
95
def __init__(self, backing_transport, root_client_path='/'):
88
def __init__(self, backing_transport):
98
91
:param backing_transport: a transport. Requests will be processed
99
92
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
104
94
# Use a ChrootTransportDecorator so that this web application won't
105
95
# accidentally let people access locations they shouldn't.
106
96
# e.g. consider a smart server request for "get /etc/passwd" or
108
98
self.chroot_server = chroot.ChrootServer(backing_transport)
109
self.chroot_server.start_server()
99
self.chroot_server.setUp()
110
100
self.backing_transport = get_transport(self.chroot_server.get_url())
111
self.root_client_path = root_client_path
112
101
# While the chroot server can technically be torn down at this point,
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
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
115
104
# bzrlib that will invoke 'get_transport' on urls rather than cloning
116
105
# around the existing transport.
117
#self.chroot_server.stop_server()
106
#self.chroot_server.tearDown()
119
108
def __call__(self, environ, start_response):
120
109
"""WSGI application callable."""
125
114
relpath = environ['bzrlib.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)
115
transport = self.backing_transport.clone(relpath)
158
116
out_buffer = StringIO()
159
117
request_data_length = int(environ['CONTENT_LENGTH'])
160
118
request_data_bytes = environ['wsgi.input'].read(request_data_length)
161
119
smart_protocol_request = self.make_request(
162
transport, out_buffer.write, request_data_bytes, adjusted_rcp)
120
transport, out_buffer.write, request_data_bytes)
163
121
if smart_protocol_request.next_read_size() != 0:
164
122
# The request appears to be incomplete, or perhaps it's just a
165
123
# newer version we don't understand. Regardless, all we can do
173
131
start_response('200 OK', headers)
174
132
return [response_data]
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)
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)
182
144
return server_protocol