~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/chroot.py

  • Committer: Alexander Belchenko
  • Date: 2007-01-30 23:05:35 UTC
  • mto: This revision was merged to the branch mainline in revision 2259.
  • Revision ID: bialix@ukr.net-20070130230535-kx1rd478rtigyc3v
standalone installer: win98 support

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
"""Implementation of Transport that prevents access to locations above a set
18
18
root.
19
19
"""
20
 
from urlparse import urlparse
21
20
 
22
21
from bzrlib import errors, urlutils
23
 
from bzrlib.transport import (
24
 
    get_transport,
25
 
    register_transport,
26
 
    Server,
27
 
    Transport,
28
 
    unregister_transport,
29
 
    )
30
22
from bzrlib.transport.decorator import TransportDecorator, DecoratorServer
31
 
from bzrlib.transport.memory import MemoryTransport
32
 
 
33
 
 
34
 
class ChrootServer(Server):
35
 
    """User space 'chroot' facility.
36
 
    
37
 
    The server's get_url returns the url for a chroot transport mapped to the
38
 
    backing transport. The url is of the form chroot-xxx:/// so parent
39
 
    directories of the backing transport are not visible. The chroot url will
40
 
    not allow '..' sequences to result in requests to the chroot affecting
41
 
    directories outside the backing transport.
42
 
    """
43
 
 
44
 
    def __init__(self, backing_transport):
45
 
        self.backing_transport = backing_transport
46
 
 
47
 
    def _factory(self, url):
48
 
        assert url.startswith(self.scheme)
49
 
        return ChrootTransport(self, url)
50
 
 
51
 
    def get_url(self):
52
 
        return self.scheme
53
 
 
54
 
    def setUp(self):
55
 
        self.scheme = 'chroot-%d:///' % id(self)
56
 
        register_transport(self.scheme, self._factory)
57
 
 
58
 
    def tearDown(self):
59
 
        unregister_transport(self.scheme, self._factory)
60
 
 
61
 
 
62
 
class ChrootTransport(Transport):
63
 
    """A ChrootTransport.
64
 
 
65
 
    Please see ChrootServer for details.
66
 
    """
67
 
 
68
 
    def __init__(self, server, base):
69
 
        self.server = server
70
 
        if not base.endswith('/'):
71
 
            base += '/'
72
 
        Transport.__init__(self, base)
73
 
        self.base_path = self.base[len(self.server.scheme)-1:]
74
 
        self.scheme = self.server.scheme
75
 
 
76
 
    def _call(self, methodname, relpath, *args):
77
 
        method = getattr(self.server.backing_transport, methodname)
78
 
        return method(self._safe_relpath(relpath), *args)
79
 
 
80
 
    def _safe_relpath(self, relpath):
81
 
        safe_relpath = self._combine_paths(self.base_path, relpath)
82
 
        assert safe_relpath.startswith('/')
83
 
        return safe_relpath[1:]
84
 
 
85
 
    # Transport methods
86
 
    def abspath(self, relpath):
87
 
        return self.scheme + self._safe_relpath(relpath)
88
 
 
 
23
 
 
24
 
 
25
class ChrootTransportDecorator(TransportDecorator):
 
26
    """A decorator that can convert any transport to be chrooted.
 
27
 
 
28
    This is requested via the 'chrooted+' prefix to get_transport().
 
29
    """
 
30
 
 
31
    def __init__(self, url, _decorated=None, chroot=None):
 
32
        super(ChrootTransportDecorator, self).__init__(url,
 
33
                _decorated=_decorated)
 
34
        if chroot is None:
 
35
            self.chroot_url = self._decorated.base
 
36
        else:
 
37
            self.chroot_url = chroot
 
38
 
 
39
    @classmethod
 
40
    def _get_url_prefix(self):
 
41
        """Chroot transport decorators are invoked via 'chroot+'"""
 
42
        return 'chroot+'
 
43
 
 
44
    def _ensure_relpath_is_child(self, relpath):
 
45
        abspath = self.abspath(relpath)
 
46
        chroot_base = self._get_url_prefix() + self.chroot_url
 
47
        real_relpath = urlutils.relative_url(chroot_base, abspath)
 
48
        if real_relpath == '..' or real_relpath.startswith('../'):
 
49
            raise errors.PathNotChild(relpath, self.chroot_url)
 
50
 
 
51
    # decorated methods
89
52
    def append_file(self, relpath, f, mode=None):
90
 
        return self._call('append_file', relpath, f, mode)
91
 
 
92
 
    def _can_roundtrip_unix_modebits(self):
93
 
        return self.server.backing_transport._can_roundtrip_unix_modebits()
94
 
 
95
 
    def clone(self, relpath):
96
 
        return ChrootTransport(self.server, self.abspath(relpath))
 
53
        self._ensure_relpath_is_child(relpath)
 
54
        return TransportDecorator.append_file(self, relpath, f, mode=mode)
 
55
 
 
56
    def append_bytes(self, relpath, bytes, mode=None):
 
57
        self._ensure_relpath_is_child(relpath)
 
58
        return TransportDecorator.append_bytes(self, relpath, bytes, mode=mode)
 
59
 
 
60
    def clone(self, offset=None):
 
61
        self._ensure_relpath_is_child(offset)
 
62
        return TransportDecorator.clone(self, offset)
97
63
 
98
64
    def delete(self, relpath):
99
 
        return self._call('delete', relpath)
 
65
        self._ensure_relpath_is_child(relpath)
 
66
        return TransportDecorator.delete(self, relpath)
100
67
 
101
68
    def delete_tree(self, relpath):
102
 
        return self._call('delete_tree', relpath)
103
 
 
104
 
    def external_url(self):
105
 
        """See bzrlib.transport.Transport.external_url."""
106
 
        # Chroots, like MemoryTransport depend on in-process
107
 
        # state and thus the base cannot simply be handed out.
108
 
        # See the base class docstring for more details and
109
 
        # possible directions. For now we return the chrooted
110
 
        # url. 
111
 
        return self.server.backing_transport.external_url()
 
69
        self._ensure_relpath_is_child(relpath)
 
70
        return TransportDecorator.delete_tree(self, relpath)
112
71
 
113
72
    def get(self, relpath):
114
 
        return self._call('get', relpath)
 
73
        self._ensure_relpath_is_child(relpath)
 
74
        return TransportDecorator.get(self, relpath)
 
75
 
 
76
    def get_bytes(self, relpath):
 
77
        self._ensure_relpath_is_child(relpath)
 
78
        return TransportDecorator.get_bytes(self, relpath)
115
79
 
116
80
    def has(self, relpath):
117
 
        return self._call('has', relpath)
118
 
 
119
 
    def iter_files_recursive(self):
120
 
        backing_transport = self.server.backing_transport.clone(
121
 
            self._safe_relpath('.'))
122
 
        return backing_transport.iter_files_recursive()
123
 
 
124
 
    def listable(self):
125
 
        return self.server.backing_transport.listable()
 
81
        self._ensure_relpath_is_child(relpath)
 
82
        return TransportDecorator.has(self, relpath)
126
83
 
127
84
    def list_dir(self, relpath):
128
 
        return self._call('list_dir', relpath)
 
85
        self._ensure_relpath_is_child(relpath)
 
86
        return TransportDecorator.list_dir(self, relpath)
129
87
 
130
88
    def lock_read(self, relpath):
131
 
        return self._call('lock_read', relpath)
 
89
        self._ensure_relpath_is_child(relpath)
 
90
        return TransportDecorator.lock_read(self, relpath)
132
91
 
133
92
    def lock_write(self, relpath):
134
 
        return self._call('lock_write', relpath)
 
93
        self._ensure_relpath_is_child(relpath)
 
94
        return TransportDecorator.lock_write(self, relpath)
135
95
 
136
96
    def mkdir(self, relpath, mode=None):
137
 
        return self._call('mkdir', relpath, mode)
 
97
        self._ensure_relpath_is_child(relpath)
 
98
        return TransportDecorator.mkdir(self, relpath, mode=mode)
138
99
 
139
 
    def open_write_stream(self, relpath, mode=None):
140
 
        return self._call('open_write_stream', relpath, mode)
 
100
    def put_bytes(self, relpath, bytes, mode=None):
 
101
        self._ensure_relpath_is_child(relpath)
 
102
        return TransportDecorator.put_bytes(self, relpath, bytes, mode=mode)
141
103
 
142
104
    def put_file(self, relpath, f, mode=None):
143
 
        return self._call('put_file', relpath, f, mode)
 
105
        self._ensure_relpath_is_child(relpath)
 
106
        return TransportDecorator.put_file(self, relpath, f, mode=mode)
144
107
 
145
108
    def rename(self, rel_from, rel_to):
146
 
        return self._call('rename', rel_from, self._safe_relpath(rel_to))
 
109
        self._ensure_relpath_is_child(rel_from)
 
110
        self._ensure_relpath_is_child(rel_to)
 
111
        return TransportDecorator.rename(self, rel_from, rel_to)
147
112
 
148
113
    def rmdir(self, relpath):
149
 
        return self._call('rmdir', relpath)
 
114
        self._ensure_relpath_is_child(relpath)
 
115
        return TransportDecorator.rmdir(self, relpath)
150
116
 
151
117
    def stat(self, relpath):
152
 
        return self._call('stat', relpath)
153
 
 
154
 
 
155
 
class TestingChrootServer(ChrootServer):
156
 
 
157
 
    def __init__(self):
158
 
        """TestingChrootServer is not usable until setUp is called."""
159
 
 
160
 
    def setUp(self, backing_server=None):
161
 
        """Setup the Chroot on backing_server."""
162
 
        if backing_server is not None:
163
 
            self.backing_transport = get_transport(backing_server.get_url())
164
 
        else:
165
 
            self.backing_transport = get_transport('.')
166
 
        ChrootServer.setUp(self)
 
118
        self._ensure_relpath_is_child(relpath)
 
119
        return TransportDecorator.stat(self, relpath)
 
120
 
 
121
 
 
122
class ChrootServer(DecoratorServer):
 
123
    """Server for the ReadonlyTransportDecorator for testing with."""
 
124
 
 
125
    def get_decorator_class(self):
 
126
        return ChrootTransportDecorator
167
127
 
168
128
 
169
129
def get_test_permutations():
170
130
    """Return the permutations to be used in testing."""
171
 
    return [(ChrootTransport, TestingChrootServer),
 
131
    return [(ChrootTransportDecorator, ChrootServer),
172
132
            ]