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