/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
2190.1.4 by John Arbash Meinel
Add ability to enable writeable bzr+http access.
30
def make_app(root, prefix, path_var, 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)
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
42
    app = SmartWSGIApp(base_transport)
43
    app = RelpathSetter(app, prefix, path_var)
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
88
    def __init__(self, backing_transport):
89
        """Constructor.
90
91
        :param backing_transport: a transport.  Requests will be processed
92
            relative to this transport.
93
        """
2018.4.11 by Andrew Bennetts
Use ChrootTransportDecorator so that the WSGI server won't let you access the entire filesystem.
94
        # Use a ChrootTransportDecorator so that this web application won't
95
        # accidentally let people access locations they shouldn't.
96
        # e.g. consider a smart server request for "get /etc/passwd" or
97
        # something.
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
98
        self.chroot_server = chroot.ChrootServer(backing_transport)
99
        self.chroot_server.setUp()
100
        self.backing_transport = get_transport(self.chroot_server.get_url())
2379.2.1 by Robert Collins
Rewritten chroot transport that prevents accidental chroot escapes when
101
        # While the chroot server can technically be torn down at this point,
102
        # as all it does is remove the scheme registration from transport's 
2379.2.3 by Robert Collins
Review feedback.
103
        # 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
104
        # bzrlib that will invoke 'get_transport' on urls rather than cloning
105
        # around the existing transport.
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
106
        #self.chroot_server.tearDown()
107
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
108
    def __call__(self, environ, start_response):
109
        """WSGI application callable."""
110
        if environ['REQUEST_METHOD'] != 'POST':
111
            start_response('405 Method not allowed', [('Allow', 'POST')])
112
            return []
113
114
        relpath = environ['bzrlib.relpath']
115
        transport = self.backing_transport.clone(relpath)
116
        out_buffer = StringIO()
117
        request_data_length = int(environ['CONTENT_LENGTH'])
118
        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.
119
        smart_protocol_request = self.make_request(
120
            transport, out_buffer.write, request_data_bytes)
2018.4.5 by Andrew Bennetts
Improvement thanks to John's review.
121
        if smart_protocol_request.next_read_size() != 0:
122
            # The request appears to be incomplete, or perhaps it's just a
123
            # newer version we don't understand.  Regardless, all we can do
124
            # is return an error response in the format of our version of the
125
            # protocol.
126
            response_data = 'error\x01incomplete request\n'
127
        else:
128
            response_data = out_buffer.getvalue()
2018.4.1 by Andrew Bennetts
Add WSGI smart server.
129
        headers = [('Content-type', 'application/octet-stream')]
130
        headers.append(("Content-Length", str(len(response_data))))
131
        start_response('200 OK', headers)
132
        return [response_data]
133
2432.2.2 by Andrew Bennetts
Smart server mediums now detect which protocol version a request is and dispatch accordingly.
134
    def make_request(self, transport, write_func, request_bytes):
135
        # XXX: This duplicates the logic in
136
        # 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.
137
        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.
138
            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.
139
            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.
140
        else:
141
            protocol_class = protocol.SmartServerRequestProtocolOne
142
        server_protocol = protocol_class(transport, write_func)
143
        server_protocol.accept_bytes(request_bytes)
144
        return server_protocol