~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/chroot.py

Fix WorkingTree4._iter_changes with pending merges and deleted files

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 clone(self, relpath):
93
 
        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)
94
63
 
95
64
    def delete(self, relpath):
96
 
        return self._call('delete', relpath)
 
65
        self._ensure_relpath_is_child(relpath)
 
66
        return TransportDecorator.delete(self, relpath)
97
67
 
98
68
    def delete_tree(self, relpath):
99
 
        return self._call('delete_tree', relpath)
 
69
        self._ensure_relpath_is_child(relpath)
 
70
        return TransportDecorator.delete_tree(self, relpath)
100
71
 
101
72
    def get(self, relpath):
102
 
        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)
103
79
 
104
80
    def has(self, relpath):
105
 
        return self._call('has', relpath)
106
 
 
107
 
    def iter_files_recursive(self):
108
 
        backing_transport = self.server.backing_transport.clone(
109
 
            self._safe_relpath('.'))
110
 
        return backing_transport.iter_files_recursive()
111
 
 
112
 
    def listable(self):
113
 
        return self.server.backing_transport.listable()
 
81
        self._ensure_relpath_is_child(relpath)
 
82
        return TransportDecorator.has(self, relpath)
114
83
 
115
84
    def list_dir(self, relpath):
116
 
        return self._call('list_dir', relpath)
 
85
        self._ensure_relpath_is_child(relpath)
 
86
        return TransportDecorator.list_dir(self, relpath)
117
87
 
118
88
    def lock_read(self, relpath):
119
 
        return self._call('lock_read', relpath)
 
89
        self._ensure_relpath_is_child(relpath)
 
90
        return TransportDecorator.lock_read(self, relpath)
120
91
 
121
92
    def lock_write(self, relpath):
122
 
        return self._call('lock_write', relpath)
 
93
        self._ensure_relpath_is_child(relpath)
 
94
        return TransportDecorator.lock_write(self, relpath)
123
95
 
124
96
    def mkdir(self, relpath, mode=None):
125
 
        return self._call('mkdir', relpath, mode)
 
97
        self._ensure_relpath_is_child(relpath)
 
98
        return TransportDecorator.mkdir(self, relpath, mode=mode)
 
99
 
 
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)
126
103
 
127
104
    def put_file(self, relpath, f, mode=None):
128
 
        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)
129
107
 
130
108
    def rename(self, rel_from, rel_to):
131
 
        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)
132
112
 
133
113
    def rmdir(self, relpath):
134
 
        return self._call('rmdir', relpath)
 
114
        self._ensure_relpath_is_child(relpath)
 
115
        return TransportDecorator.rmdir(self, relpath)
135
116
 
136
117
    def stat(self, relpath):
137
 
        return self._call('stat', relpath)
138
 
 
139
 
 
140
 
class TestingChrootServer(ChrootServer):
141
 
 
142
 
    def __init__(self):
143
 
        """TestingChrootServer is not usable until setUp is called."""
144
 
 
145
 
    def setUp(self, backing_server=None):
146
 
        """Setup the Chroot on backing_server."""
147
 
        if backing_server is not None:
148
 
            self.backing_transport = get_transport(backing_server.get_url())
149
 
        else:
150
 
            self.backing_transport = get_transport('.')
151
 
        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
152
127
 
153
128
 
154
129
def get_test_permutations():
155
130
    """Return the permutations to be used in testing."""
156
 
    return [(ChrootTransport, TestingChrootServer),
 
131
    return [(ChrootTransportDecorator, ChrootServer),
157
132
            ]