| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 787359d commit 2625ed9
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -37,6 +37,10 @@ class NoSuchPathError(GitError, OSError): | |||
| 37 | 37 | """Thrown if a path could not be access by the system.""" | |
| 38 | 38 | ||
| 39 | 39 | ||
| 40 | + class UnsafeOptionsUsedError(GitError): | ||
| 41 | + """Thrown if unsafe protocols or options are passed without overridding.""" | ||
| 42 | + | ||
| 43 | + | ||
| 40 | 44 | class CommandError(GitError): | |
| 41 | 45 | """Base class for exceptions thrown at every stage of `Popen()` execution. | |
| 42 | 46 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -21,7 +21,12 @@ | |||
| 21 | 21 | ) | |
| 22 | 22 | from git.config import GitConfigParser | |
| 23 | 23 | from git.db import GitCmdObjectDB | |
| 24 | - from git.exc import InvalidGitRepositoryError, NoSuchPathError, GitCommandError | ||
| 24 | + from git.exc import ( | ||
| 25 | + GitCommandError, | ||
| 26 | + InvalidGitRepositoryError, | ||
| 27 | + NoSuchPathError, | ||
| 28 | + UnsafeOptionsUsedError, | ||
| 29 | + ) | ||
| 25 | 30 | from git.index import IndexFile | |
| 26 | 31 | from git.objects import Submodule, RootModule, Commit | |
| 27 | 32 | from git.refs import HEAD, Head, Reference, TagReference | |
@@ -128,6 +133,7 @@ class Repo(object): | |||
| 128 | 133 | re_envvars = re.compile(r"(\$(\{\s?)?[a-zA-Z_]\w*(\}\s?)?|%\s?[a-zA-Z_]\w*\s?%)") | |
| 129 | 134 | re_author_committer_start = re.compile(r"^(author|committer)") | |
| 130 | 135 | re_tab_full_line = re.compile(r"^\t(.*)$") | |
| 136 | + re_config_protocol_option = re.compile(r"-[-]?c(|onfig)\s+protocol\.", re.I) | ||
| 131 | 137 | ||
| 132 | 138 | # invariants | |
| 133 | 139 | # represents the configuration level of a configuration file | |
@@ -1215,11 +1221,27 @@ def _clone( | |||
| 1215 | 1221 | # END handle remote repo | |
| 1216 | 1222 | return repo | |
| 1217 | 1223 | ||
| 1224 | + @classmethod | ||
| 1225 | + def unsafe_options( | ||
| 1226 | + cls, | ||
| 1227 | + url: str, | ||
| 1228 | + multi_options: Optional[List[str]] = None, | ||
| 1229 | + ) -> bool: | ||
| 1230 | + if "ext::" in url: | ||
| 1231 | + return True | ||
| 1232 | + if multi_options is not None: | ||
| 1233 | + if any(["--upload-pack" in m for m in multi_options]): | ||
| 1234 | + return True | ||
| 1235 | + if any([re.match(cls.re_config_protocol_option, m) for m in multi_options]): | ||
| 1236 | + return True | ||
| 1237 | + return False | ||
| 1238 | + | ||
| 1218 | 1239 | def clone( | |
| 1219 | 1240 | self, | |
| 1220 | 1241 | path: PathLike, | |
| 1221 | 1242 | progress: Optional[Callable] = None, | |
| 1222 | 1243 | multi_options: Optional[List[str]] = None, | |
| 1244 | + unsafe_protocols: bool = False, | ||
| 1223 | 1245 | **kwargs: Any, | |
| 1224 | 1246 | ) -> "Repo": | |
| 1225 | 1247 | """Create a clone from this repository. | |
@@ -1230,12 +1252,15 @@ def clone( | |||
| 1230 | 1252 | option per list item which is passed exactly as specified to clone. | |
| 1231 | 1253 | For example ['--config core.filemode=false', '--config core.ignorecase', | |
| 1232 | 1254 | '--recurse-submodule=repo1_path', '--recurse-submodule=repo2_path'] | |
| 1255 | + :param unsafe_protocols: Allow unsafe protocols to be used, like ext | ||
| 1233 | 1256 | :param kwargs: | |
| 1234 | 1257 | * odbt = ObjectDatabase Type, allowing to determine the object database | |
| 1235 | 1258 | implementation used by the returned Repo instance | |
| 1236 | 1259 | * All remaining keyword arguments are given to the git-clone command | |
| 1237 | 1260 | ||
| 1238 | 1261 | :return: ``git.Repo`` (the newly cloned repo)""" | |
| 1262 | + if not unsafe_protocols and self.unsafe_options(path, multi_options): | ||
| 1263 | + raise UnsafeOptionsUsedError(f"{path} requires unsafe_protocols flag") | ||
| 1239 | 1264 | return self._clone( | |
| 1240 | 1265 | self.git, | |
| 1241 | 1266 | self.common_dir, | |
@@ -1254,6 +1279,7 @@ def clone_from( | |||
| 1254 | 1279 | progress: Optional[Callable] = None, | |
| 1255 | 1280 | env: Optional[Mapping[str, str]] = None, | |
| 1256 | 1281 | multi_options: Optional[List[str]] = None, | |
| 1282 | + unsafe_protocols: bool = False, | ||
| 1257 | 1283 | **kwargs: Any, | |
| 1258 | 1284 | ) -> "Repo": | |
| 1259 | 1285 | """Create a clone from the given URL | |
@@ -1268,11 +1294,14 @@ def clone_from( | |||
| 1268 | 1294 | If you want to unset some variable, consider providing empty string | |
| 1269 | 1295 | as its value. | |
| 1270 | 1296 | :param multi_options: See ``clone`` method | |
| 1297 | + :param unsafe_protocols: Allow unsafe protocols to be used, like ext | ||
| 1271 | 1298 | :param kwargs: see the ``clone`` method | |
| 1272 | 1299 | :return: Repo instance pointing to the cloned directory""" | |
| 1273 | 1300 | git = cls.GitCommandWrapperType(os.getcwd()) | |
| 1274 | 1301 | if env is not None: | |
| 1275 | 1302 | git.update_environment(**env) | |
| 1303 | + if not unsafe_protocols and cls.unsafe_options(url, multi_options): | ||
| 1304 | + raise UnsafeOptionsUsedError(f"{url} requires unsafe_protocols flag") | ||
| 1276 | 1305 | return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs) | |
| 1277 | 1306 | ||
| 1278 | 1307 | def archive( | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,6 +13,7 @@ | |||
| 13 | 13 | import pickle | |
| 14 | 14 | import sys | |
| 15 | 15 | import tempfile | |
| 16 | + import uuid | ||
| 16 | 17 | from unittest import mock, skipIf, SkipTest | |
| 17 | 18 | ||
| 18 | 19 | import pytest | |
@@ -37,6 +38,7 @@ | |||
| 37 | 38 | ) | |
| 38 | 39 | from git.exc import ( | |
| 39 | 40 | BadObject, | |
| 41 | + UnsafeOptionsUsedError, | ||
| 40 | 42 | ) | |
| 41 | 43 | from git.repo.fun import touch | |
| 42 | 44 | from test.lib import TestBase, with_rw_repo, fixture | |
@@ -263,6 +265,40 @@ def test_leaking_password_in_clone_logs(self, rw_dir): | |||
| 263 | 265 | to_path=rw_dir, | |
| 264 | 266 | ) | |
| 265 | 267 | ||
| 268 | + def test_unsafe_options(self): | ||
| 269 | + self.assertFalse(Repo.unsafe_options("github.com/deploy/deploy")) | ||
| 270 | + | ||
| 271 | + def test_unsafe_options_ext_url(self): | ||
| 272 | + self.assertTrue(Repo.unsafe_options("ext::ssh")) | ||
| 273 | + | ||
| 274 | + def test_unsafe_options_multi_options_upload_pack(self): | ||
| 275 | + self.assertTrue(Repo.unsafe_options("", ["--upload-pack='touch foo'"])) | ||
| 276 | + | ||
| 277 | + def test_unsafe_options_multi_options_config_user(self): | ||
| 278 | + self.assertFalse(Repo.unsafe_options("", ["--config user"])) | ||
| 279 | + | ||
| 280 | + def test_unsafe_options_multi_options_config_protocol(self): | ||
| 281 | + self.assertTrue(Repo.unsafe_options("", ["--config protocol.foo"])) | ||
| 282 | + | ||
| 283 | + def test_clone_from_forbids_helper_urls_by_default(self): | ||
| 284 | + with self.assertRaises(UnsafeOptionsUsedError): | ||
| 285 | + Repo.clone_from("ext::sh -c touch% /tmp/foo", "tmp") | ||
| 286 | + | ||
| 287 | + @with_rw_repo("HEAD") | ||
| 288 | + def test_clone_from_allow_unsafe(self, repo): | ||
| 289 | + bad_filename = pathlib.Path(f'{tempfile.gettempdir()}/{uuid.uuid4()}') | ||
| 290 | + bad_url = f'ext::sh -c touch% {bad_filename}' | ||
| 291 | + try: | ||
| 292 | + repo.clone_from( | ||
| 293 | + bad_url, 'tmp', | ||
| 294 | + multi_options=["-c protocol.ext.allow=always"], | ||
| 295 | + unsafe_protocols=True | ||
| 296 | + ) | ||
| 297 | + except GitCommandError: | ||
| 298 | + pass | ||
| 299 | + self.assertTrue(bad_filename.is_file()) | ||
| 300 | + bad_filename.unlink() | ||
| 301 | + | ||
| 266 | 302 | @with_rw_repo("HEAD") | |
| 267 | 303 | def test_max_chunk_size(self, repo): | |
| 268 | 304 | class TestOutputStream(TestBase): | |
| Back | FazBrowse Home | New Git URL |
0 commit comments