~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/chroot.py

  • Committer: Aaron Bentley
  • Date: 2007-02-06 14:52:16 UTC
  • mfrom: (2266 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2268.
  • Revision ID: abentley@panoramicfeedback.com-20070206145216-fcpi8o3ufvuzwbp9
Merge bzr.dev

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
 
        return ChrootTransport(self, url)
49
 
 
50
 
    def get_url(self):
51
 
        return self.scheme
52
 
 
53
 
    def setUp(self):
54
 
        self.scheme = 'chroot-%d:///' % id(self)
55
 
        register_transport(self.scheme, self._factory)
56
 
 
57
 
    def tearDown(self):
58
 
        unregister_transport(self.scheme, self._factory)
59
 
 
60
 
 
61
 
class ChrootTransport(Transport):
62
 
    """A ChrootTransport.
63
 
 
64
 
    Please see ChrootServer for details.
65
 
    """
66
 
 
67
 
    def __init__(self, server, base):
68
 
        self.server = server
69
 
        if not base.endswith('/'):
70
 
            base += '/'
71
 
        Transport.__init__(self, base)
72
 
        self.base_path = self.base[len(self.server.scheme)-1:]
73
 
        self.scheme = self.server.scheme
74
 
 
75
 
    def _call(self, methodname, relpath, *args):
76
 
        method = getattr(self.server.backing_transport, methodname)
77
 
        return method(self._safe_relpath(relpath), *args)
78
 
 
79
 
    def _safe_relpath(self, relpath):
80
 
        safe_relpath = self._combine_paths(self.base_path, relpath)
81
 
        if not safe_relpath.startswith('/'):
82
 
            raise ValueError(safe_relpath)
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 is_readonly(self):
120
 
        return self.server.backing_transport.is_readonly()
121
 
 
122
 
    def iter_files_recursive(self):
123
 
        backing_transport = self.server.backing_transport.clone(
124
 
            self._safe_relpath('.'))
125
 
        return backing_transport.iter_files_recursive()
126
 
 
127
 
    def listable(self):
128
 
        return self.server.backing_transport.listable()
 
81
        self._ensure_relpath_is_child(relpath)
 
82
        return TransportDecorator.has(self, relpath)
129
83
 
130
84
    def list_dir(self, relpath):
131
 
        return self._call('list_dir', relpath)
 
85
        self._ensure_relpath_is_child(relpath)
 
86
        return TransportDecorator.list_dir(self, relpath)
132
87
 
133
88
    def lock_read(self, relpath):
134
 
        return self._call('lock_read', relpath)
 
89
        self._ensure_relpath_is_child(relpath)
 
90
        return TransportDecorator.lock_read(self, relpath)
135
91
 
136
92
    def lock_write(self, relpath):
137
 
        return self._call('lock_write', relpath)
 
93
        self._ensure_relpath_is_child(relpath)
 
94
        return TransportDecorator.lock_write(self, relpath)
138
95
 
139
96
    def mkdir(self, relpath, mode=None):
140
 
        return self._call('mkdir', relpath, mode)
 
97
        self._ensure_relpath_is_child(relpath)
 
98
        return TransportDecorator.mkdir(self, relpath, mode=mode)
141
99
 
142
 
    def open_write_stream(self, relpath, mode=None):
143
 
        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)
144
103
 
145
104
    def put_file(self, relpath, f, mode=None):
146
 
        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)
147
107
 
148
108
    def rename(self, rel_from, rel_to):
149
 
        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)
150
112
 
151
113
    def rmdir(self, relpath):
152
 
        return self._call('rmdir', relpath)
 
114
        self._ensure_relpath_is_child(relpath)
 
115
        return TransportDecorator.rmdir(self, relpath)
153
116
 
154
117
    def stat(self, relpath):
155
 
        return self._call('stat', relpath)
156
 
 
157
 
 
158
 
class TestingChrootServer(ChrootServer):
159
 
 
160
 
    def __init__(self):
161
 
        """TestingChrootServer is not usable until setUp is called."""
162
 
 
163
 
    def setUp(self, backing_server=None):
164
 
        """Setup the Chroot on backing_server."""
165
 
        if backing_server is not None:
166
 
            self.backing_transport = get_transport(backing_server.get_url())
167
 
        else:
168
 
            self.backing_transport = get_transport('.')
169
 
        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
170
127
 
171
128
 
172
129
def get_test_permutations():
173
130
    """Return the permutations to be used in testing."""
174
 
    return [(ChrootTransport, TestingChrootServer),
 
131
    return [(ChrootTransportDecorator, ChrootServer),
175
132
            ]