13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""Implementation of Transport that prevents access to locations above a set
21
from bzrlib.transport import (
27
class ChrootServer(pathfilter.PathFilteringServer):
28
"""User space 'chroot' facility.
30
The server's get_url returns the url for a chroot transport mapped to the
31
backing transport. The url is of the form chroot-xxx:/// so parent
32
directories of the backing transport are not visible. The chroot url will
33
not allow '..' sequences to result in requests to the chroot affecting
34
directories outside the backing transport.
36
PathFilteringServer does all the path sanitation needed to enforce a
37
chroot, so this is a simple subclass of PathFilteringServer that ignores
41
def __init__(self, backing_transport):
42
pathfilter.PathFilteringServer.__init__(self, backing_transport, None)
44
def _factory(self, url):
45
return ChrootTransport(self, url)
47
def start_server(self):
48
self.scheme = 'chroot-%d:///' % id(self)
49
register_transport(self.scheme, self._factory)
52
class ChrootTransport(pathfilter.PathFilteringTransport):
55
Please see ChrootServer for details.
58
def _filter(self, relpath):
59
# A simplified version of PathFilteringTransport's _filter that omits
60
# the call to self.server.filter_func.
61
return self._relpath_from_server_root(relpath)
20
from urlparse import urlparse
22
from bzrlib import errors, urlutils
23
from bzrlib.transport.decorator import TransportDecorator, DecoratorServer
26
class ChrootTransportDecorator(TransportDecorator):
27
"""A decorator that can convert any transport to be chrooted.
29
This is requested via the 'chroot+' prefix to get_transport().
31
:ivar chroot_url: the root of this chroot
32
:ivar chroot_relative: this transport's location relative to the chroot
33
root. e.g. A chroot_relative of '/' means this location is the same as
37
def __init__(self, url, _decorated=None, chroot=None):
38
super(ChrootTransportDecorator, self).__init__(url,
39
_decorated=_decorated)
41
self.chroot_url = self._decorated.base
43
self.chroot_url = chroot
44
self.chroot_relative = '/' + self._decorated.base[len(self.chroot_url):]
47
def _get_url_prefix(self):
48
"""Chroot transport decorators are invoked via 'chroot+'"""
51
def _ensure_relpath_is_child(self, relpath):
52
abspath = self.abspath(relpath)
53
chroot_base = self._get_url_prefix() + self.chroot_url
54
real_relpath = urlutils.relative_url(chroot_base, abspath)
55
if real_relpath == '..' or real_relpath.startswith('../'):
56
raise errors.PathNotChild(relpath, self.chroot_url)
59
def abspath(self, relpath):
61
url = urlutils.join('fake:///', relpath)
62
except errors.InvalidURLJoin:
63
raise errors.PathNotChild(relpath, self.chroot_url)
64
normalised_abspath = url[len('fake:///'):]
65
return self._get_url_prefix() + self.chroot_url + normalised_abspath[1:]
67
def append_file(self, relpath, f, mode=None):
68
self._ensure_relpath_is_child(relpath)
69
return TransportDecorator.append_file(self, relpath, f, mode=mode)
71
def append_bytes(self, relpath, bytes, mode=None):
72
self._ensure_relpath_is_child(relpath)
73
return TransportDecorator.append_bytes(self, relpath, bytes, mode=mode)
75
def clone(self, offset=None):
76
if offset is None: return self
77
# the new URL we want to clone to is
78
# self.chroot_url + an adjusted self.chroot_relative, with the leading
80
new_relpath = urlutils.joinpath(self.chroot_relative, offset)
81
assert new_relpath.startswith('/')
82
new_url = self.chroot_url + new_relpath[1:]
83
# Clone the decorated transport according to this new path.
84
assert new_url.startswith(self.chroot_url), (
85
'new_url (%r) does not start with %r'
86
% (new_url, self._decorated.base))
87
path = urlutils.relative_url(self._decorated.base, new_url)
88
decorated_clone = self._decorated.clone(path)
89
return ChrootTransportDecorator(self._get_url_prefix() + new_url,
90
decorated_clone, self.chroot_url)
92
def delete(self, relpath):
93
self._ensure_relpath_is_child(relpath)
94
return TransportDecorator.delete(self, relpath)
96
def delete_tree(self, relpath):
97
self._ensure_relpath_is_child(relpath)
98
return TransportDecorator.delete_tree(self, relpath)
100
def get(self, relpath):
101
self._ensure_relpath_is_child(relpath)
102
return TransportDecorator.get(self, relpath)
104
def get_bytes(self, relpath):
105
self._ensure_relpath_is_child(relpath)
106
return TransportDecorator.get_bytes(self, relpath)
108
def has(self, relpath):
109
self._ensure_relpath_is_child(relpath)
110
return TransportDecorator.has(self, relpath)
112
def list_dir(self, relpath):
113
self._ensure_relpath_is_child(relpath)
114
return TransportDecorator.list_dir(self, relpath)
116
def lock_read(self, relpath):
117
self._ensure_relpath_is_child(relpath)
118
return TransportDecorator.lock_read(self, relpath)
120
def lock_write(self, relpath):
121
self._ensure_relpath_is_child(relpath)
122
return TransportDecorator.lock_write(self, relpath)
124
def mkdir(self, relpath, mode=None):
125
self._ensure_relpath_is_child(relpath)
126
return TransportDecorator.mkdir(self, relpath, mode=mode)
128
def put_bytes(self, relpath, bytes, mode=None):
129
self._ensure_relpath_is_child(relpath)
130
return TransportDecorator.put_bytes(self, relpath, bytes, mode=mode)
132
def put_file(self, relpath, f, mode=None):
133
self._ensure_relpath_is_child(relpath)
134
return TransportDecorator.put_file(self, relpath, f, mode=mode)
136
def rename(self, rel_from, rel_to):
137
self._ensure_relpath_is_child(rel_from)
138
self._ensure_relpath_is_child(rel_to)
139
return TransportDecorator.rename(self, rel_from, rel_to)
141
def rmdir(self, relpath):
142
self._ensure_relpath_is_child(relpath)
143
return TransportDecorator.rmdir(self, relpath)
145
def stat(self, relpath):
146
self._ensure_relpath_is_child(relpath)
147
return TransportDecorator.stat(self, relpath)
150
class ChrootServer(DecoratorServer):
151
"""Server for the ReadonlyTransportDecorator for testing with."""
153
def get_decorator_class(self):
154
return ChrootTransportDecorator
64
157
def get_test_permutations():
65
158
"""Return the permutations to be used in testing."""
66
from bzrlib.tests import test_server
67
return [(ChrootTransport, test_server.TestingChrootServer)]
159
return [(ChrootTransportDecorator, ChrootServer),