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