~bzr-pqm/bzr/bzr.dev

4763.2.4 by John Arbash Meinel
merge bzr.2.1 in preparation for NEWS entry.
1
# Copyright (C) 2006-2010 Canonical Ltd
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
16
17
"""WSGI application for bzr HTTP smart server.
18
19
For more information about WSGI, see PEP 333:
20
    http://www.python.org/dev/peps/pep-0333/
21
"""
22
6379.6.7 by Jelmer Vernooij
Move importing from future until after doc string, otherwise the doc string will disappear.
23
from __future__ import absolute_import
24
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
25
from cStringIO import StringIO
26
6123.2.1 by Jelmer Vernooij
Remove unused imports, fix import of error.
27
from bzrlib.smart import medium
2018.5.21 by Andrew Bennetts
Move bzrlib.transport.smart to bzrlib.smart
28
from bzrlib.transport import chroot, get_transport
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
29
from bzrlib.urlutils import local_path_to_url
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
30
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
31
3708.1.1 by Marius Kruger
wsgi.make_app can now optionally load plugins (used by bzr+http://)
32
def make_app(root, prefix, path_var='REQUEST_URI', readonly=True,
3708.1.2 by Marius Kruger
wsgi.make_app can now optionally do normal logging (used by bzr+http://)
33
    load_plugins=True, enable_logging=True):
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
34
    """Convenience function to construct a WSGI bzr smart server.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
35
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
36
    :param root: a local path that requests will be relative to.
37
    :param prefix: See RelpathSetter.
38
    :param path_var: See RelpathSetter.
39
    """
2190.1.4 by John Arbash Meinel
Add ability to enable writeable bzr+http access.
40
    local_url = local_path_to_url(root)
41
    if readonly:
42
        base_transport = get_transport('readonly+' + local_url)
43
    else:
44
        base_transport = get_transport(local_url)
3708.1.1 by Marius Kruger
wsgi.make_app can now optionally load plugins (used by bzr+http://)
45
    if load_plugins:
46
        from bzrlib.plugin import load_plugins
47
        load_plugins()
3708.1.2 by Marius Kruger
wsgi.make_app can now optionally do normal logging (used by bzr+http://)
48
    if enable_logging:
49
        import bzrlib.trace
50
        bzrlib.trace.enable_default_logging()
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
51
    app = SmartWSGIApp(base_transport, prefix)
52
    app = RelpathSetter(app, '', path_var)
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
53
    return app
54
55
56
class RelpathSetter(object):
57
    """WSGI middleware to set 'bzrlib.relpath' in the environ.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
58
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
59
    Different servers can invoke a SmartWSGIApp in different ways.  This
60
    middleware allows an adminstrator to configure how to the SmartWSGIApp will
61
    determine what path it should be serving for a given request for many common
62
    situations.
63
64
    For example, a request for "/some/prefix/repo/branch/.bzr/smart" received by
65
    a typical Apache and mod_fastcgi configuration will set `REQUEST_URI` to
66
    "/some/prefix/repo/branch/.bzr/smart".  A RelpathSetter with
67
    prefix="/some/prefix/" and path_var="REQUEST_URI" will set that request's
68
    'bzrlib.relpath' variable to "repo/branch".
69
    """
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
70
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
71
    def __init__(self, app, prefix='', path_var='REQUEST_URI'):
72
        """Constructor.
73
74
        :param app: WSGI app to wrap, e.g. a SmartWSGIApp instance.
75
        :param path_var: the variable in the WSGI environ to calculate the
76
            'bzrlib.relpath' variable from.
77
        :param prefix: a prefix to strip from the variable specified in
78
            path_var before setting 'bzrlib.relpath'.
79
        """
80
        self.app = app
81
        self.prefix = prefix
82
        self.path_var = path_var
83
84
    def __call__(self, environ, start_response):
85
        path = environ[self.path_var]
86
        suffix = '/.bzr/smart'
87
        if not (path.startswith(self.prefix) and path.endswith(suffix)):
2705.2.1 by Matt Nordhoff
wsgi.RelpathSetter sent an empty dict of headers instead of an empty list in 404 error responses.
88
            start_response('404 Not Found', [])
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
89
            return []
90
        environ['bzrlib.relpath'] = path[len(self.prefix):-len(suffix)]
91
        return self.app(environ, start_response)
92
93
94
class SmartWSGIApp(object):
95
    """A WSGI application for the bzr smart server."""
96
2692.1.1 by Andrew Bennetts
Add translate_client_path method to SmartServerRequest.
97
    def __init__(self, backing_transport, root_client_path='/'):
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
98
        """Constructor.
99
100
        :param backing_transport: a transport.  Requests will be processed
101
            relative to this transport.
2692.1.1 by Andrew Bennetts
Add translate_client_path method to SmartServerRequest.
102
        :param root_client_path: the client path that maps to the root of
103
            backing_transport.  This is used to interpret relpaths received from
104
            the client.
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
105
        """
5699.5.1 by Max Bowsher
Expunge a few lingering references to ChrootTransportDecorator and the chroot+
106
        # Use a ChrootServer so that this web application won't
2018.4.11 by Andrew Bennetts
Use ChrootTransportDecorator so that the WSGI server won't let you access the entire filesystem.
107
        # accidentally let people access locations they shouldn't.
108
        # e.g. consider a smart server request for "get /etc/passwd" or
109
        # something.
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
110
        self.chroot_server = chroot.ChrootServer(backing_transport)
4934.3.3 by Martin Pool
Rename Server.setUp to Server.start_server
111
        self.chroot_server.start_server()
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
112
        self.backing_transport = get_transport(self.chroot_server.get_url())
2692.1.1 by Andrew Bennetts
Add translate_client_path method to SmartServerRequest.
113
        self.root_client_path = root_client_path
2379.2.1 by Robert Collins
Rewritten chroot transport that prevents accidental chroot escapes when
114
        # While the chroot server can technically be torn down at this point,
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
115
        # as all it does is remove the scheme registration from transport's
116
        # protocol dictionary, we don't *just in case* there are parts of
2379.2.1 by Robert Collins
Rewritten chroot transport that prevents accidental chroot escapes when
117
        # bzrlib that will invoke 'get_transport' on urls rather than cloning
118
        # around the existing transport.
4934.3.1 by Martin Pool
Rename Server.tearDown to .stop_server
119
        #self.chroot_server.stop_server()
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
120
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
121
    def __call__(self, environ, start_response):
122
        """WSGI application callable."""
123
        if environ['REQUEST_METHOD'] != 'POST':
124
            start_response('405 Method not allowed', [('Allow', 'POST')])
125
            return []
126
127
        relpath = environ['bzrlib.relpath']
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
128
129
        if not relpath.startswith('/'):
130
            relpath = '/' + relpath
131
        if not relpath.endswith('/'):
132
            relpath += '/'
133
2692.1.16 by Andrew Bennetts
Improve comments.
134
        # Compare the HTTP path (relpath) and root_client_path, and calculate
135
        # new relpath and root_client_path accordingly, to be used to build the
136
        # request.
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
137
        if relpath.startswith(self.root_client_path):
2692.1.16 by Andrew Bennetts
Improve comments.
138
            # The relpath traverses all of the mandatory root client path.
139
            # Remove the root_client_path from the relpath, and set
140
            # adjusted_tcp to None to tell the request handler that no further
141
            # path translation is required.
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
142
            adjusted_rcp = None
143
            adjusted_relpath = relpath[len(self.root_client_path):]
144
        elif self.root_client_path.startswith(relpath):
145
            # The relpath traverses some of the mandatory root client path.
2692.1.16 by Andrew Bennetts
Improve comments.
146
            # Subtract the relpath from the root_client_path, and set the
147
            # relpath to '.'.
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
148
            adjusted_rcp = '/' + self.root_client_path[len(relpath):]
149
            adjusted_relpath = '.'
150
        else:
151
            adjusted_rcp = self.root_client_path
152
            adjusted_relpath = relpath
153
154
        if adjusted_relpath.startswith('/'):
155
            adjusted_relpath = adjusted_relpath[1:]
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
156
        if adjusted_relpath.startswith('/'):
157
            raise AssertionError(adjusted_relpath)
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
158
159
        transport = self.backing_transport.clone(adjusted_relpath)
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
160
        out_buffer = StringIO()
161
        request_data_length = int(environ['CONTENT_LENGTH'])
162
        request_data_bytes = environ['wsgi.input'].read(request_data_length)
2432.2.2 by Andrew Bennetts
Smart server mediums now detect which protocol version a request is and dispatch accordingly.
163
        smart_protocol_request = self.make_request(
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
164
            transport, out_buffer.write, request_data_bytes, adjusted_rcp)
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
165
        if smart_protocol_request.next_read_size() != 0:
166
            # The request appears to be incomplete, or perhaps it's just a
167
            # newer version we don't understand.  Regardless, all we can do
168
            # is return an error response in the format of our version of the
169
            # protocol.
170
            response_data = 'error\x01incomplete request\n'
171
        else:
172
            response_data = out_buffer.getvalue()
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
173
        headers = [('Content-type', 'application/octet-stream')]
174
        headers.append(("Content-Length", str(len(response_data))))
175
        start_response('200 OK', headers)
176
        return [response_data]
177
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
178
    def make_request(self, transport, write_func, request_bytes, rcp):
3245.4.16 by Andrew Bennetts
Remove duplication of request version identification logic in wsgi.py
179
        protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
180
            request_bytes)
4760.1.1 by Andrew Bennetts
Add optional jail_root argument to SmartServerRequest and friends, and use it in the WSGI glue. Allows opening branches in shared repos via bzr+http (assuming the repo should be accessible).
181
        server_protocol = protocol_factory(
182
            transport, write_func, rcp, self.backing_transport)
3245.4.16 by Andrew Bennetts
Remove duplication of request version identification logic in wsgi.py
183
        server_protocol.accept_bytes(unused_bytes)
2432.2.2 by Andrew Bennetts
Smart server mediums now detect which protocol version a request is and dispatch accordingly.
184
        return server_protocol