/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
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
7045.2.6 by Jelmer Vernooij
Fix some wsgi tests.
23
from io import BytesIO
24
6670.4.16 by Jelmer Vernooij
Move smart to breezy.bzr.
25
from ...bzr.smart import medium
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
26
from ...transport import chroot, get_transport
27
from ...urlutils import local_path_to_url
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
28
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
29
3708.1.1 by Marius Kruger
wsgi.make_app can now optionally load plugins (used by bzr+http://)
30
def make_app(root, prefix, path_var='REQUEST_URI', readonly=True,
7143.15.2 by Jelmer Vernooij
Run autopep8.
31
             load_plugins=True, enable_logging=True):
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
32
    """Convenience function to construct a WSGI bzr smart server.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
33
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
34
    :param root: a local path that requests will be relative to.
35
    :param prefix: See RelpathSetter.
36
    :param path_var: See RelpathSetter.
37
    """
2190.1.4 by John Arbash Meinel
Add ability to enable writeable bzr+http access.
38
    local_url = local_path_to_url(root)
39
    if readonly:
40
        base_transport = get_transport('readonly+' + local_url)
41
    else:
42
        base_transport = get_transport(local_url)
3708.1.1 by Marius Kruger
wsgi.make_app can now optionally load plugins (used by bzr+http://)
43
    if load_plugins:
6624 by Jelmer Vernooij
Merge Python3 porting work ('py3 pokes')
44
        from ...plugin import load_plugins
3708.1.1 by Marius Kruger
wsgi.make_app can now optionally load plugins (used by bzr+http://)
45
        load_plugins()
3708.1.2 by Marius Kruger
wsgi.make_app can now optionally do normal logging (used by bzr+http://)
46
    if enable_logging:
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
47
        import breezy.trace
48
        breezy.trace.enable_default_logging()
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
49
    app = SmartWSGIApp(base_transport, prefix)
50
    app = RelpathSetter(app, '', path_var)
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
51
    return app
52
53
54
class RelpathSetter(object):
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
55
    """WSGI middleware to set 'breezy.relpath' in the environ.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
56
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
57
    Different servers can invoke a SmartWSGIApp in different ways.  This
58
    middleware allows an adminstrator to configure how to the SmartWSGIApp will
59
    determine what path it should be serving for a given request for many common
60
    situations.
61
62
    For example, a request for "/some/prefix/repo/branch/.bzr/smart" received by
63
    a typical Apache and mod_fastcgi configuration will set `REQUEST_URI` to
64
    "/some/prefix/repo/branch/.bzr/smart".  A RelpathSetter with
65
    prefix="/some/prefix/" and path_var="REQUEST_URI" will set that request's
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
66
    'breezy.relpath' variable to "repo/branch".
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
67
    """
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
68
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
69
    def __init__(self, app, prefix='', path_var='REQUEST_URI'):
70
        """Constructor.
71
72
        :param app: WSGI app to wrap, e.g. a SmartWSGIApp instance.
73
        :param path_var: the variable in the WSGI environ to calculate the
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
74
            'breezy.relpath' variable from.
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
75
        :param prefix: a prefix to strip from the variable specified in
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
76
            path_var before setting 'breezy.relpath'.
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
77
        """
78
        self.app = app
79
        self.prefix = prefix
80
        self.path_var = path_var
81
82
    def __call__(self, environ, start_response):
83
        path = environ[self.path_var]
84
        suffix = '/.bzr/smart'
85
        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.
86
            start_response('404 Not Found', [])
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
87
            return []
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
88
        environ['breezy.relpath'] = path[len(self.prefix):-len(suffix)]
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
89
        return self.app(environ, start_response)
90
91
92
class SmartWSGIApp(object):
93
    """A WSGI application for the bzr smart server."""
94
2692.1.1 by Andrew Bennetts
Add translate_client_path method to SmartServerRequest.
95
    def __init__(self, backing_transport, root_client_path='/'):
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
96
        """Constructor.
97
98
        :param backing_transport: a transport.  Requests will be processed
99
            relative to this transport.
2692.1.1 by Andrew Bennetts
Add translate_client_path method to SmartServerRequest.
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
102
            the client.
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
103
        """
5699.5.1 by Max Bowsher
Expunge a few lingering references to ChrootTransportDecorator and the chroot+
104
        # 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.
105
        # accidentally let people access locations they shouldn't.
106
        # e.g. consider a smart server request for "get /etc/passwd" or
107
        # something.
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
108
        self.chroot_server = chroot.ChrootServer(backing_transport)
4934.3.3 by Martin Pool
Rename Server.setUp to Server.start_server
109
        self.chroot_server.start_server()
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
110
        self.backing_transport = get_transport(self.chroot_server.get_url())
2692.1.1 by Andrew Bennetts
Add translate_client_path method to SmartServerRequest.
111
        self.root_client_path = root_client_path
2379.2.1 by Robert Collins
Rewritten chroot transport that prevents accidental chroot escapes when
112
        # 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
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
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
115
        # breezy that will invoke 'get_transport' on urls rather than cloning
2379.2.1 by Robert Collins
Rewritten chroot transport that prevents accidental chroot escapes when
116
        # around the existing transport.
7143.15.2 by Jelmer Vernooij
Run autopep8.
117
        # self.chroot_server.stop_server()
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
118
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
119
    def __call__(self, environ, start_response):
120
        """WSGI application callable."""
121
        if environ['REQUEST_METHOD'] != 'POST':
122
            start_response('405 Method not allowed', [('Allow', 'POST')])
123
            return []
124
6622.1.34 by Jelmer Vernooij
Rename brzlib => breezy.
125
        relpath = environ['breezy.relpath']
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
126
127
        if not relpath.startswith('/'):
128
            relpath = '/' + relpath
129
        if not relpath.endswith('/'):
130
            relpath += '/'
131
2692.1.16 by Andrew Bennetts
Improve comments.
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
134
        # request.
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
135
        if relpath.startswith(self.root_client_path):
2692.1.16 by Andrew Bennetts
Improve comments.
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.
6963.1.1 by Jelmer Vernooij
Fix a bunch of tests on python3.
140
            adjusted_rcp = '.'
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
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.
2692.1.16 by Andrew Bennetts
Improve comments.
144
            # Subtract the relpath from the root_client_path, and set the
145
            # relpath to '.'.
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
146
            adjusted_rcp = '/' + self.root_client_path[len(relpath):]
147
            adjusted_relpath = '.'
148
        else:
149
            adjusted_rcp = self.root_client_path
150
            adjusted_relpath = relpath
151
152
        if adjusted_relpath.startswith('/'):
153
            adjusted_relpath = adjusted_relpath[1:]
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
154
        if adjusted_relpath.startswith('/'):
155
            raise AssertionError(adjusted_relpath)
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
156
157
        transport = self.backing_transport.clone(adjusted_relpath)
6621.22.2 by Martin
Use BytesIO or StringIO from bzrlib.sixish
158
        out_buffer = BytesIO()
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
159
        request_data_length = int(environ['CONTENT_LENGTH'])
160
        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.
161
        smart_protocol_request = self.make_request(
6963.1.1 by Jelmer Vernooij
Fix a bunch of tests on python3.
162
            transport, out_buffer.write, request_data_bytes,
163
            adjusted_rcp)
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
164
        if smart_protocol_request.next_read_size() != 0:
165
            # The request appears to be incomplete, or perhaps it's just a
166
            # newer version we don't understand.  Regardless, all we can do
167
            # is return an error response in the format of our version of the
168
            # protocol.
6963.1.1 by Jelmer Vernooij
Fix a bunch of tests on python3.
169
            response_data = b'error\x01incomplete request\n'
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
170
        else:
171
            response_data = out_buffer.getvalue()
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
172
        headers = [('Content-type', 'application/octet-stream')]
173
        headers.append(("Content-Length", str(len(response_data))))
174
        start_response('200 OK', headers)
175
        return [response_data]
176
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
177
    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
178
        protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
179
            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).
180
        server_protocol = protocol_factory(
181
            transport, write_func, rcp, self.backing_transport)
3245.4.16 by Andrew Bennetts
Remove duplication of request version identification logic in wsgi.py
182
        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.
183
        return server_protocol