/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
1
# Copyright (C) 2006 Canonical Ltd
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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
23
from cStringIO import StringIO
24
2018.5.33 by Andrew Bennetts
Fix two trivial test failures.
25
from bzrlib.smart import protocol
2018.5.21 by Andrew Bennetts
Move bzrlib.transport.smart to bzrlib.smart
26
from bzrlib.transport import chroot, get_transport
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
27
from bzrlib.urlutils import local_path_to_url
28
    
29
2692.1.21 by Andrew Bennetts
Make 'REQUEST_URI' the default value for make_app's path_var arg.
30
def make_app(root, prefix, path_var='REQUEST_URI', readonly=True):
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
31
    """Convenience function to construct a WSGI bzr smart server.
32
    
33
    :param root: a local path that requests will be relative to.
34
    :param prefix: See RelpathSetter.
35
    :param path_var: See RelpathSetter.
36
    """
2190.1.4 by John Arbash Meinel
Add ability to enable writeable bzr+http access.
37
    local_url = local_path_to_url(root)
38
    if readonly:
39
        base_transport = get_transport('readonly+' + local_url)
40
    else:
41
        base_transport = get_transport(local_url)
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
42
    app = SmartWSGIApp(base_transport, prefix)
43
    app = RelpathSetter(app, '', path_var)
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
44
    return app
45
46
47
class RelpathSetter(object):
48
    """WSGI middleware to set 'bzrlib.relpath' in the environ.
49
    
50
    Different servers can invoke a SmartWSGIApp in different ways.  This
51
    middleware allows an adminstrator to configure how to the SmartWSGIApp will
52
    determine what path it should be serving for a given request for many common
53
    situations.
54
55
    For example, a request for "/some/prefix/repo/branch/.bzr/smart" received by
56
    a typical Apache and mod_fastcgi configuration will set `REQUEST_URI` to
57
    "/some/prefix/repo/branch/.bzr/smart".  A RelpathSetter with
58
    prefix="/some/prefix/" and path_var="REQUEST_URI" will set that request's
59
    'bzrlib.relpath' variable to "repo/branch".
60
    """
61
    
62
    def __init__(self, app, prefix='', path_var='REQUEST_URI'):
63
        """Constructor.
64
65
        :param app: WSGI app to wrap, e.g. a SmartWSGIApp instance.
66
        :param path_var: the variable in the WSGI environ to calculate the
67
            'bzrlib.relpath' variable from.
68
        :param prefix: a prefix to strip from the variable specified in
69
            path_var before setting 'bzrlib.relpath'.
70
        """
71
        self.app = app
72
        self.prefix = prefix
73
        self.path_var = path_var
74
75
    def __call__(self, environ, start_response):
76
        path = environ[self.path_var]
77
        suffix = '/.bzr/smart'
78
        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.
79
            start_response('404 Not Found', [])
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
80
            return []
81
        environ['bzrlib.relpath'] = path[len(self.prefix):-len(suffix)]
82
        return self.app(environ, start_response)
83
84
85
class SmartWSGIApp(object):
86
    """A WSGI application for the bzr smart server."""
87
2692.1.1 by Andrew Bennetts
Add translate_client_path method to SmartServerRequest.
88
    def __init__(self, backing_transport, root_client_path='/'):
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
89
        """Constructor.
90
91
        :param backing_transport: a transport.  Requests will be processed
92
            relative to this transport.
2692.1.1 by Andrew Bennetts
Add translate_client_path method to SmartServerRequest.
93
        :param root_client_path: the client path that maps to the root of
94
            backing_transport.  This is used to interpret relpaths received from
95
            the client.
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
96
        """
2018.4.11 by Andrew Bennetts
Use ChrootTransportDecorator so that the WSGI server won't let you access the entire filesystem.
97
        # Use a ChrootTransportDecorator so that this web application won't
98
        # accidentally let people access locations they shouldn't.
99
        # e.g. consider a smart server request for "get /etc/passwd" or
100
        # something.
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
101
        self.chroot_server = chroot.ChrootServer(backing_transport)
102
        self.chroot_server.setUp()
103
        self.backing_transport = get_transport(self.chroot_server.get_url())
2692.1.1 by Andrew Bennetts
Add translate_client_path method to SmartServerRequest.
104
        self.root_client_path = root_client_path
2379.2.1 by Robert Collins
Rewritten chroot transport that prevents accidental chroot escapes when
105
        # While the chroot server can technically be torn down at this point,
106
        # as all it does is remove the scheme registration from transport's 
2379.2.3 by Robert Collins
Review feedback.
107
        # 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
108
        # bzrlib that will invoke 'get_transport' on urls rather than cloning
109
        # around the existing transport.
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
110
        #self.chroot_server.tearDown()
111
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
112
    def __call__(self, environ, start_response):
113
        """WSGI application callable."""
114
        if environ['REQUEST_METHOD'] != 'POST':
115
            start_response('405 Method not allowed', [('Allow', 'POST')])
116
            return []
117
118
        relpath = environ['bzrlib.relpath']
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
119
120
        if not relpath.startswith('/'):
121
            relpath = '/' + relpath
122
        if not relpath.endswith('/'):
123
            relpath += '/'
124
2692.1.16 by Andrew Bennetts
Improve comments.
125
        # Compare the HTTP path (relpath) and root_client_path, and calculate
126
        # new relpath and root_client_path accordingly, to be used to build the
127
        # request.
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
128
        if relpath.startswith(self.root_client_path):
2692.1.16 by Andrew Bennetts
Improve comments.
129
            # The relpath traverses all of the mandatory root client path.
130
            # Remove the root_client_path from the relpath, and set
131
            # adjusted_tcp to None to tell the request handler that no further
132
            # path translation is required.
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
133
            adjusted_rcp = None
134
            adjusted_relpath = relpath[len(self.root_client_path):]
135
        elif self.root_client_path.startswith(relpath):
136
            # The relpath traverses some of the mandatory root client path.
2692.1.16 by Andrew Bennetts
Improve comments.
137
            # Subtract the relpath from the root_client_path, and set the
138
            # relpath to '.'.
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
139
            adjusted_rcp = '/' + self.root_client_path[len(relpath):]
140
            adjusted_relpath = '.'
141
        else:
142
            adjusted_rcp = self.root_client_path
143
            adjusted_relpath = relpath
144
145
        if adjusted_relpath.startswith('/'):
146
            adjusted_relpath = adjusted_relpath[1:]
147
        assert not adjusted_relpath.startswith('/')
148
149
        transport = self.backing_transport.clone(adjusted_relpath)
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
150
        out_buffer = StringIO()
151
        request_data_length = int(environ['CONTENT_LENGTH'])
152
        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.
153
        smart_protocol_request = self.make_request(
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
154
            transport, out_buffer.write, request_data_bytes, adjusted_rcp)
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
155
        if smart_protocol_request.next_read_size() != 0:
156
            # The request appears to be incomplete, or perhaps it's just a
157
            # newer version we don't understand.  Regardless, all we can do
158
            # is return an error response in the format of our version of the
159
            # protocol.
160
            response_data = 'error\x01incomplete request\n'
161
        else:
162
            response_data = out_buffer.getvalue()
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
163
        headers = [('Content-type', 'application/octet-stream')]
164
        headers.append(("Content-Length", str(len(response_data))))
165
        start_response('200 OK', headers)
166
        return [response_data]
167
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
168
    def make_request(self, transport, write_func, request_bytes, rcp):
2432.2.2 by Andrew Bennetts
Smart server mediums now detect which protocol version a request is and dispatch accordingly.
169
        # XXX: This duplicates the logic in
170
        # SmartServerStreamMedium._build_protocol.
2432.2.7 by Andrew Bennetts
Use less confusing version strings, and define REQUEST_VERSION_TWO/RESPONSE_VERSION_TWO constants for them.
171
        if request_bytes.startswith(protocol.REQUEST_VERSION_TWO):
2432.2.2 by Andrew Bennetts
Smart server mediums now detect which protocol version a request is and dispatch accordingly.
172
            protocol_class = protocol.SmartServerRequestProtocolTwo
2432.2.7 by Andrew Bennetts
Use less confusing version strings, and define REQUEST_VERSION_TWO/RESPONSE_VERSION_TWO constants for them.
173
            request_bytes = request_bytes[len(protocol.REQUEST_VERSION_TWO):]
2432.2.2 by Andrew Bennetts
Smart server mediums now detect which protocol version a request is and dispatch accordingly.
174
        else:
175
            protocol_class = protocol.SmartServerRequestProtocolOne
2692.1.1 by Andrew Bennetts
Add translate_client_path method to SmartServerRequest.
176
        server_protocol = protocol_class(
2692.1.14 by Andrew Bennetts
All WSGI tests passing, and manual testing works too.
177
            transport, write_func, rcp)
2432.2.2 by Andrew Bennetts
Smart server mediums now detect which protocol version a request is and dispatch accordingly.
178
        server_protocol.accept_bytes(request_bytes)
179
        return server_protocol