From da928f0320fd862b3a13fb0d35e38e40452f2184 Mon Sep 17 00:00:00 2001 From: mshqq Date: Mon, 17 Aug 2026 04:04:41 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=82=D0=B0=D0=B9=D0=BF=20=D1=85=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D1=8B=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=8B=D0=BC=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=BC=20=D0=B2=20clone.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/scanner/clone.py | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/app/scanner/clone.py b/app/scanner/clone.py index cb50dbe..65855fb 100644 --- a/app/scanner/clone.py +++ b/app/scanner/clone.py @@ -2,24 +2,22 @@ import shutil import subprocess import tempfile -from urllib.parse import urlparse from app.scanner.exceptions import RepoCloneError, RepoTooLargeError +from app.utils import is_allowed_url SCAN_TEMP_DIR = "" MAX_REPO_SIZE_MB = 500 GIT_TIMEOUT_SECONDS = 30 -ALLOWED_HOSTS = ("github.com", "gitlab.com") - def _check_repo_size(path: str) -> int: - total = 0 + total: int = 0 for dirpath, dirnames, filenames in os.walk(path, followlinks=False): for name in filenames: - file_path = os.path.join(dirpath, name) + file_path: str = os.path.join(dirpath, name) if os.path.islink(file_path): continue try: @@ -47,27 +45,8 @@ def _run_git(args: list[str], cwd: str, timeout: int) -> None: raise RepoCloneError(f"git {' '.join(args)} timed out after {timeout}s") from e -def _is_allowed_url(url: str) -> bool: - if not url: - return False - - parsed = urlparse(url) - if parsed.scheme in ("http", "https"): - host = parsed.hostname - - return host is not None and host.lower() in ALLOWED_HOSTS - - if url.startswith("git@"): - rest = url[len("git@") :] - host, sep, path = rest.partition(":") - - return bool(sep) and bool(host) and bool(path) and host.lower() in ALLOWED_HOSTS - - return False - - def clone_repo(url: str, commit_sha: str | None = None) -> tuple[str, str]: - if not _is_allowed_url(url): + if not is_allowed_url(url): raise RepoCloneError("URL не разрешён (нужен http(s):// или git@host:...)") ref: str = commit_sha or "HEAD" @@ -83,10 +62,10 @@ def clone_repo(url: str, commit_sha: str | None = None) -> tuple[str, str]: ) _run_git(["checkout", "FETCH_HEAD"], temp_folder_path, GIT_TIMEOUT_SECONDS) - resolved_sha = _get_head_sha(temp_folder_path) + resolved_sha: str = _get_head_sha(temp_folder_path) - size_bytes = _check_repo_size(temp_folder_path) - max_bytes = MAX_REPO_SIZE_MB * 1024 * 1024 + size_bytes: int = _check_repo_size(temp_folder_path) + max_bytes: int = MAX_REPO_SIZE_MB * 1024 * 1024 if size_bytes > max_bytes: raise RepoTooLargeError( From ab1d60587946dce8e8f31c83954c2c41c7ed9691 Mon Sep 17 00:00:00 2001 From: mshqq Date: Mon, 17 Aug 2026 04:05:15 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B8=20is=5Fallowed=5Furl=20=D0=B8=D0=B7=20?= =?UTF-8?q?clone.py=20=D0=B2=20utils.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/utils.py b/app/utils.py index 2313d48..00e9f74 100644 --- a/app/utils.py +++ b/app/utils.py @@ -1,5 +1,27 @@ from datetime import datetime, timezone +from urllib.parse import ParseResult, urlparse + +ALLOWED_HOSTS = ("github.com", "gitlab.com") def utcnow() -> datetime: return datetime.now(timezone.utc) + + +def is_allowed_url(url: str) -> bool: + if not url: + return False + + parsed: ParseResult = urlparse(url) + if parsed.scheme in ("http", "https"): + host: str | None = parsed.hostname + + return host is not None and host.lower() in ALLOWED_HOSTS + + if url.startswith("git@"): + rest: str = url[len("git@") :] + host, sep, path = rest.partition(":") + + return bool(sep) and bool(host) and bool(path) and host.lower() in ALLOWED_HOSTS + + return False From c4b308c517182de85a350371893c10b9a79b6ae9 Mon Sep 17 00:00:00 2001 From: mshqq Date: Mon, 17 Aug 2026 04:05:42 +0900 Subject: [PATCH 3/3] =?UTF-8?q?test:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20test=5Fclone.py=20=D0=BF=D0=BE=D0=B4=20?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BE=D0=B5=20=D1=80=D0=B0=D1=81=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20is=5Fallowed=5Furl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_clone.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_clone.py b/tests/test_clone.py index 3bd6ada..68e5c66 100644 --- a/tests/test_clone.py +++ b/tests/test_clone.py @@ -3,8 +3,9 @@ import pytest import app.scanner.clone as clone_module -from app.scanner.clone import _is_allowed_url, cleanup, clone_repo +from app.scanner.clone import cleanup, clone_repo from app.scanner.exceptions import RepoCloneError, RepoTooLargeError +from app.utils import is_allowed_url def test_clone_repo_real_github(): @@ -56,7 +57,7 @@ def test_clone_nonexist_repo_raises(): ], ) def test_is_allowed_url(url, status): - assert _is_allowed_url(url) is status + assert is_allowed_url(url) is status def test_cleanup_removes_directory(tmp_path):