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