6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
1 |
# Copyright (C) 2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
16 |
||
6402.3.10
by Jelmer Vernooij
Name changes suggested by mgz. |
17 |
"""Branch opening with URL-based restrictions."""
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
18 |
|
6402.3.5
by Jelmer Vernooij
Avoid assert. |
19 |
from __future__ import absolute_import |
20 |
||
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
21 |
import threading |
22 |
||
23 |
from bzrlib import ( |
|
24 |
errors, |
|
25 |
urlutils, |
|
26 |
)
|
|
27 |
from bzrlib.branch import Branch |
|
6402.3.3
by Jelmer Vernooij
Simplify safe open a bit more. |
28 |
from bzrlib.controldir import ( |
29 |
ControlDir, |
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
30 |
)
|
31 |
||
32 |
||
6402.3.2
by Jelmer Vernooij
bzrify |
33 |
class BadUrl(errors.BzrError): |
34 |
||
35 |
_fmt = "Tried to access a branch from bad URL %(url)s." |
|
36 |
||
37 |
||
38 |
class BranchReferenceForbidden(errors.BzrError): |
|
39 |
||
40 |
_fmt = ("Trying to mirror a branch reference and the branch type " |
|
41 |
"does not allow references.") |
|
42 |
||
43 |
||
44 |
class BranchLoopError(errors.BzrError): |
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
45 |
"""Encountered a branch cycle.
|
46 |
||
47 |
A URL may point to a branch reference or it may point to a stacked branch.
|
|
48 |
In either case, it's possible for there to be a cycle in these references,
|
|
49 |
and this exception is raised when we detect such a cycle.
|
|
50 |
"""
|
|
51 |
||
6402.3.2
by Jelmer Vernooij
bzrify |
52 |
_fmt = "Encountered a branch cycle""" |
53 |
||
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
54 |
|
55 |
class BranchOpenPolicy(object): |
|
56 |
"""Policy on how to open branches.
|
|
57 |
||
6402.3.11
by Jelmer Vernooij
Avoid the word safe. |
58 |
In particular, a policy determines which branches are okay to open by
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
59 |
checking their URLs and deciding whether or not to follow branch
|
60 |
references.
|
|
61 |
"""
|
|
62 |
||
63 |
def should_follow_references(self): |
|
64 |
"""Whether we traverse references when mirroring.
|
|
65 |
||
66 |
Subclasses must override this method.
|
|
67 |
||
68 |
If we encounter a branch reference and this returns false, an error is
|
|
69 |
raised.
|
|
70 |
||
71 |
:returns: A boolean to indicate whether to follow a branch reference.
|
|
72 |
"""
|
|
73 |
raise NotImplementedError(self.should_follow_references) |
|
74 |
||
75 |
def transform_fallback_location(self, branch, url): |
|
76 |
"""Validate, maybe modify, 'url' to be used as a stacked-on location.
|
|
77 |
||
78 |
:param branch: The branch that is being opened.
|
|
79 |
:param url: The URL that the branch provides for its stacked-on
|
|
80 |
location.
|
|
81 |
:return: (new_url, check) where 'new_url' is the URL of the branch to
|
|
82 |
actually open and 'check' is true if 'new_url' needs to be
|
|
83 |
validated by check_and_follow_branch_reference.
|
|
84 |
"""
|
|
85 |
raise NotImplementedError(self.transform_fallback_location) |
|
86 |
||
87 |
def check_one_url(self, url): |
|
6402.3.11
by Jelmer Vernooij
Avoid the word safe. |
88 |
"""Check a URL.
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
89 |
|
90 |
Subclasses must override this method.
|
|
91 |
||
92 |
:param url: The source URL to check.
|
|
93 |
:raise BadUrl: subclasses are expected to raise this or a subclass
|
|
6402.3.11
by Jelmer Vernooij
Avoid the word safe. |
94 |
when it finds a URL it deems to be unacceptable.
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
95 |
"""
|
96 |
raise NotImplementedError(self.check_one_url) |
|
97 |
||
98 |
||
6402.3.8
by Jelmer Vernooij
make BlacklistPolicy private |
99 |
class _BlacklistPolicy(BranchOpenPolicy): |
100 |
"""Branch policy that forbids certain URLs.
|
|
101 |
||
102 |
This doesn't cope with various alternative spellings of URLs,
|
|
103 |
with e.g. url encoding. It's mostly useful for tests.
|
|
104 |
"""
|
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
105 |
|
6402.3.11
by Jelmer Vernooij
Avoid the word safe. |
106 |
def __init__(self, should_follow_references, bad_urls=None): |
107 |
if bad_urls is None: |
|
108 |
bad_urls = set() |
|
109 |
self._bad_urls = bad_urls |
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
110 |
self._should_follow_references = should_follow_references |
111 |
||
112 |
def should_follow_references(self): |
|
113 |
return self._should_follow_references |
|
114 |
||
115 |
def check_one_url(self, url): |
|
6402.3.11
by Jelmer Vernooij
Avoid the word safe. |
116 |
if url in self._bad_urls: |
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
117 |
raise BadUrl(url) |
118 |
||
119 |
def transform_fallback_location(self, branch, url): |
|
120 |
"""See `BranchOpenPolicy.transform_fallback_location`.
|
|
121 |
||
122 |
This class is not used for testing our smarter stacking features so we
|
|
123 |
just do the simplest thing: return the URL that would be used anyway
|
|
124 |
and don't check it.
|
|
125 |
"""
|
|
126 |
return urlutils.join(branch.base, url), False |
|
127 |
||
128 |
||
6402.3.8
by Jelmer Vernooij
make BlacklistPolicy private |
129 |
class AcceptAnythingPolicy(_BlacklistPolicy): |
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
130 |
"""Accept anything, to make testing easier."""
|
131 |
||
132 |
def __init__(self): |
|
133 |
super(AcceptAnythingPolicy, self).__init__(True, set()) |
|
134 |
||
135 |
||
136 |
class WhitelistPolicy(BranchOpenPolicy): |
|
137 |
"""Branch policy that only allows certain URLs."""
|
|
138 |
||
139 |
def __init__(self, should_follow_references, allowed_urls=None, |
|
140 |
check=False): |
|
141 |
if allowed_urls is None: |
|
142 |
allowed_urls = [] |
|
143 |
self.allowed_urls = set(url.rstrip('/') for url in allowed_urls) |
|
144 |
self.check = check |
|
145 |
||
146 |
def should_follow_references(self): |
|
147 |
return self._should_follow_references |
|
148 |
||
149 |
def check_one_url(self, url): |
|
150 |
if url.rstrip('/') not in self.allowed_urls: |
|
151 |
raise BadUrl(url) |
|
152 |
||
153 |
def transform_fallback_location(self, branch, url): |
|
154 |
"""See `BranchOpenPolicy.transform_fallback_location`.
|
|
155 |
||
156 |
Here we return the URL that would be used anyway and optionally check
|
|
157 |
it.
|
|
158 |
"""
|
|
159 |
return urlutils.join(branch.base, url), self.check |
|
160 |
||
161 |
||
162 |
class SingleSchemePolicy(BranchOpenPolicy): |
|
163 |
"""Branch open policy that rejects URLs not on the given scheme."""
|
|
164 |
||
165 |
def __init__(self, allowed_scheme): |
|
166 |
self.allowed_scheme = allowed_scheme |
|
167 |
||
168 |
def should_follow_references(self): |
|
169 |
return True |
|
170 |
||
171 |
def transform_fallback_location(self, branch, url): |
|
172 |
return urlutils.join(branch.base, url), True |
|
173 |
||
174 |
def check_one_url(self, url): |
|
6402.3.11
by Jelmer Vernooij
Avoid the word safe. |
175 |
"""Check that `url` is okay to open."""
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
176 |
if urlutils.URL.from_string(str(url)).scheme != self.allowed_scheme: |
177 |
raise BadUrl(url) |
|
178 |
||
179 |
||
6402.3.10
by Jelmer Vernooij
Name changes suggested by mgz. |
180 |
class BranchOpener(object): |
181 |
"""Branch opener which uses a URL policy.
|
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
182 |
|
183 |
All locations that are opened (stacked-on branches, references) are
|
|
184 |
checked against a policy object.
|
|
185 |
||
186 |
The policy object is expected to have the following methods:
|
|
187 |
* check_one_url
|
|
188 |
* should_follow_references
|
|
189 |
* transform_fallback_location
|
|
190 |
"""
|
|
191 |
||
192 |
_threading_data = threading.local() |
|
193 |
||
194 |
def __init__(self, policy, probers=None): |
|
6402.3.10
by Jelmer Vernooij
Name changes suggested by mgz. |
195 |
"""Create a new BranchOpener.
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
196 |
|
197 |
:param policy: The opener policy to use.
|
|
198 |
:param probers: Optional list of probers to allow.
|
|
199 |
Defaults to local and remote bzr probers.
|
|
200 |
"""
|
|
201 |
self.policy = policy |
|
202 |
self._seen_urls = set() |
|
6402.3.3
by Jelmer Vernooij
Simplify safe open a bit more. |
203 |
self.probers = probers |
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
204 |
|
205 |
@classmethod
|
|
206 |
def install_hook(cls): |
|
207 |
"""Install the ``transform_fallback_location`` hook.
|
|
208 |
||
209 |
This is done at module import time, but transform_fallback_locationHook
|
|
210 |
doesn't do anything unless the `_active_openers` threading.Local
|
|
211 |
object has a 'opener' attribute in this thread.
|
|
212 |
||
213 |
This is in a module-level function rather than performed at module
|
|
6402.3.10
by Jelmer Vernooij
Name changes suggested by mgz. |
214 |
level so that it can be called in setUp for testing `BranchOpener`
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
215 |
as bzrlib.tests.TestCase.setUp clears hooks.
|
216 |
"""
|
|
217 |
Branch.hooks.install_named_hook( |
|
218 |
'transform_fallback_location', |
|
219 |
cls.transform_fallback_locationHook, |
|
6402.3.10
by Jelmer Vernooij
Name changes suggested by mgz. |
220 |
'BranchOpener.transform_fallback_locationHook') |
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
221 |
|
222 |
def check_and_follow_branch_reference(self, url): |
|
6402.3.11
by Jelmer Vernooij
Avoid the word safe. |
223 |
"""Check URL (and possibly the referenced URL).
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
224 |
|
225 |
This method checks that `url` passes the policy's `check_one_url`
|
|
226 |
method, and if `url` refers to a branch reference, it checks whether
|
|
227 |
references are allowed and whether the reference's URL passes muster
|
|
228 |
also -- recursively, until a real branch is found.
|
|
229 |
||
230 |
:param url: URL to check
|
|
231 |
:raise BranchLoopError: If the branch references form a loop.
|
|
232 |
:raise BranchReferenceForbidden: If this opener forbids branch
|
|
233 |
references.
|
|
234 |
"""
|
|
235 |
while True: |
|
236 |
if url in self._seen_urls: |
|
237 |
raise BranchLoopError() |
|
238 |
self._seen_urls.add(url) |
|
239 |
self.policy.check_one_url(url) |
|
240 |
next_url = self.follow_reference(url) |
|
241 |
if next_url is None: |
|
242 |
return url |
|
243 |
url = next_url |
|
244 |
if not self.policy.should_follow_references(): |
|
245 |
raise BranchReferenceForbidden(url) |
|
246 |
||
247 |
@classmethod
|
|
248 |
def transform_fallback_locationHook(cls, branch, url): |
|
249 |
"""Installed as the 'transform_fallback_location' Branch hook.
|
|
250 |
||
251 |
This method calls `transform_fallback_location` on the policy object and
|
|
252 |
either returns the url it provides or passes it back to
|
|
253 |
check_and_follow_branch_reference.
|
|
254 |
"""
|
|
255 |
try: |
|
256 |
opener = getattr(cls._threading_data, "opener") |
|
257 |
except AttributeError: |
|
258 |
return url |
|
259 |
new_url, check = opener.policy.transform_fallback_location(branch, url) |
|
260 |
if check: |
|
261 |
return opener.check_and_follow_branch_reference(new_url) |
|
262 |
else: |
|
263 |
return new_url |
|
264 |
||
265 |
def run_with_transform_fallback_location_hook_installed( |
|
266 |
self, callable, *args, **kw): |
|
6402.3.5
by Jelmer Vernooij
Avoid assert. |
267 |
if (self.transform_fallback_locationHook not in |
268 |
Branch.hooks['transform_fallback_location']): |
|
269 |
raise AssertionError("hook not installed") |
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
270 |
self._threading_data.opener = self |
271 |
try: |
|
272 |
return callable(*args, **kw) |
|
273 |
finally: |
|
274 |
del self._threading_data.opener |
|
275 |
# We reset _seen_urls here to avoid multiple calls to open giving
|
|
276 |
# spurious loop exceptions.
|
|
277 |
self._seen_urls = set() |
|
278 |
||
279 |
def follow_reference(self, url): |
|
280 |
"""Get the branch-reference value at the specified url.
|
|
281 |
||
282 |
This exists as a separate method only to be overriden in unit tests.
|
|
283 |
"""
|
|
6402.3.3
by Jelmer Vernooij
Simplify safe open a bit more. |
284 |
bzrdir = ControlDir.open(url, probers=self.probers) |
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
285 |
return bzrdir.get_branch_reference() |
286 |
||
287 |
def open(self, url): |
|
6402.3.11
by Jelmer Vernooij
Avoid the word safe. |
288 |
"""Open the Bazaar branch at url, first checking it.
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
289 |
|
6402.3.11
by Jelmer Vernooij
Avoid the word safe. |
290 |
What is acceptable means is defined by the policy's `follow_reference` and
|
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
291 |
`check_one_url` methods.
|
292 |
"""
|
|
6402.3.2
by Jelmer Vernooij
bzrify |
293 |
if type(url) != str: |
294 |
raise TypeError |
|
295 |
||
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
296 |
url = self.check_and_follow_branch_reference(url) |
297 |
||
298 |
def open_branch(url): |
|
6402.3.3
by Jelmer Vernooij
Simplify safe open a bit more. |
299 |
dir = ControlDir.open(url, probers=self.probers) |
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
300 |
return dir.open_branch() |
301 |
return self.run_with_transform_fallback_location_hook_installed( |
|
302 |
open_branch, url) |
|
303 |
||
304 |
||
6402.3.10
by Jelmer Vernooij
Name changes suggested by mgz. |
305 |
def open_only_scheme(allowed_scheme, url): |
6402.3.1
by Jelmer Vernooij
Add safe_open class to bzr. |
306 |
"""Open the branch at `url`, only accessing URLs on `allowed_scheme`.
|
307 |
||
308 |
:raises BadUrl: An attempt was made to open a URL that was not on
|
|
309 |
`allowed_scheme`.
|
|
310 |
"""
|
|
6402.3.10
by Jelmer Vernooij
Name changes suggested by mgz. |
311 |
return BranchOpener(SingleSchemePolicy(allowed_scheme)).open(url) |
312 |
||
313 |
||
314 |
BranchOpener.install_hook() |