| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -728,6 +728,8 @@ def _preprocess_add_items( | |||
| 728 | 728 | else: | |
| 729 | 729 | raise TypeError("Invalid Type: %r" % item) | |
| 730 | 730 | # END for each item | |
| 731 | + # Source paths must be safe to read, but their recorded names may be rewritten. | ||
| 732 | + # Apply mode-dependent restrictions to the final entries in add(). | ||
| 731 | 733 | for entry in entries: | |
| 732 | 734 | _validate_repo_path(entry.path) | |
| 733 | 735 | return paths, entries | |
@@ -1026,7 +1028,7 @@ def handle_null_entries(self: "IndexFile") -> None: | |||
| 1026 | 1028 | # FINALIZE | |
| 1027 | 1029 | # Add the new entries to this instance. | |
| 1028 | 1030 | for entry in entries_added: | |
| 1029 | - _validate_repo_path(entry.path) | ||
| 1031 | + _validate_repo_path(entry.path, entry.mode) | ||
| 1030 | 1032 | for entry in entries_added: | |
| 1031 | 1033 | self.entries[(entry.path, 0)] = IndexEntry.from_base(entry) | |
| 1032 | 1034 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -279,7 +279,7 @@ def write_cache( | |||
| 279 | 279 | ||
| 280 | 280 | # Body | |
| 281 | 281 | for entry in entries: | |
| 282 | - _validate_repo_path(entry.path) | ||
| 282 | + _validate_repo_path(entry.path, entry.mode) | ||
| 283 | 283 | beginoffset = tell() | |
| 284 | 284 | write(entry.ctime_bytes) # ctime | |
| 285 | 285 | write(entry.mtime_bytes) # mtime | |
@@ -394,7 +394,7 @@ def read_cache( | |||
| 394 | 394 | if terminator != b"\0": | |
| 395 | 395 | raise ValueError("Unterminated index entry path") | |
| 396 | 396 | path = path_bytes.decode(defenc) | |
| 397 | - _validate_repo_path(path) | ||
| 397 | + _validate_repo_path(path, mode) | ||
| 398 | 398 | ||
| 399 | 399 | real_size = (tell() - beginoffset + 7) & ~7 | |
| 400 | 400 | padding_size = beginoffset + real_size - tell() | |
@@ -462,7 +462,7 @@ def write_tree_from_cache( | |||
| 462 | 462 | """ | |
| 463 | 463 | if si == 0: | |
| 464 | 464 | for entry in entries[sl]: | |
| 465 | - _validate_repo_path(entry.path) | ||
| 465 | + _validate_repo_path(entry.path, entry.mode) | ||
| 466 | 466 | tree_items: List["TreeCacheTup"] = [] | |
| 467 | 467 | ||
| 468 | 468 | ci = sl.start | |
@@ -510,7 +510,7 @@ def write_tree_from_cache( | |||
| 510 | 510 | ||
| 511 | 511 | ||
| 512 | 512 | def _tree_entry_to_baseindexentry(tree_entry: "TreeCacheTup", stage: int) -> BaseIndexEntry: | |
| 513 | - _validate_repo_path(tree_entry[2]) | ||
| 513 | + _validate_repo_path(tree_entry[2], tree_entry[1]) | ||
| 514 | 514 | return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2])) | |
| 515 | 515 | ||
| 516 | 516 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -39,11 +39,11 @@ | |||
| 39 | 39 | # --------------------------------------------------- | |
| 40 | 40 | ||
| 41 | 41 | ||
| 42 | - def _validate_tree_entry_name(name: str) -> None: | ||
| 42 | + def _validate_tree_entry_name(name: str, mode: Union[int, None] = None) -> None: | ||
| 43 | 43 | if "/" in name: | |
| 44 | 44 | raise ValueError("Tree entry names must not contain '/' characters") | |
| 45 | 45 | # A tree name is a component, not a rooted path; a colon cannot select a drive. | |
| 46 | - _validate_repo_path("tree/" + name) | ||
| 46 | + _validate_repo_path("tree/" + name, mode) | ||
| 47 | 47 | ||
| 48 | 48 | ||
| 49 | 49 | def tree_to_stream(entries: Sequence[EntryTup], write: Callable[["ReadableBuffer"], Union[int, None]]) -> None: | |
@@ -82,7 +82,7 @@ def tree_to_stream(entries: Sequence[EntryTup], write: Callable[["ReadableBuffer | |||
| 82 | 82 | name_bytes = name.encode(defenc) | |
| 83 | 83 | else: | |
| 84 | 84 | name_bytes = name # type: ignore[unreachable] # check runtime types - is always str? | |
| 85 | - _validate_tree_entry_name(safe_decode(name_bytes)) | ||
| 85 | + _validate_tree_entry_name(safe_decode(name_bytes), mode) | ||
| 86 | 86 | write(b"".join((mode_str, b" ", name_bytes, b"\0", binsha))) | |
| 87 | 87 | # END for each item | |
| 88 | 88 | ||
@@ -112,7 +112,7 @@ def tree_entries_from_data(data: bytes) -> List[EntryTup]: | |||
| 112 | 112 | if name_end < 0 or name_end + 21 > len(data): | |
| 113 | 113 | raise ValueError("Truncated tree entry") | |
| 114 | 114 | name = safe_decode(bytes(data[mode_end + 1 : name_end])) | |
| 115 | - _validate_tree_entry_name(name) | ||
| 115 | + _validate_tree_entry_name(name, mode) | ||
| 116 | 116 | offset = name_end + 21 | |
| 117 | 117 | out.append((bytes(data[name_end + 1 : offset]), mode, name)) | |
| 118 | 118 | return out | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -110,7 +110,7 @@ def add(self, sha: bytes, mode: int, name: str, force: bool = False) -> "TreeMod | |||
| 110 | 110 | :return: | |
| 111 | 111 | self | |
| 112 | 112 | """ | |
| 113 | - _validate_tree_entry_name(name) | ||
| 113 | + _validate_tree_entry_name(name, mode) | ||
| 114 | 114 | if (mode >> 12) not in Tree._map_id_to_type: | |
| 115 | 115 | raise ValueError("Invalid object type according to mode %o" % mode) | |
| 116 | 116 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -29,63 +29,62 @@ | |||
| 29 | 29 | if sys.platform == "win32": | |
| 30 | 30 | __all__.append("to_native_path_windows") | |
| 31 | 31 | ||
| 32 | - from abc import abstractmethod | ||
| 33 | 32 | import contextlib | |
| 34 | - from functools import wraps | ||
| 35 | 33 | import getpass | |
| 36 | 34 | import logging | |
| 37 | 35 | import ntpath | |
| 38 | 36 | import os | |
| 39 | 37 | import os.path as osp | |
| 40 | - from pathlib import Path | ||
| 41 | 38 | import platform | |
| 42 | 39 | import re | |
| 43 | 40 | import shutil | |
| 44 | 41 | import stat | |
| 45 | 42 | import subprocess | |
| 46 | 43 | import time | |
| 47 | - from urllib.parse import urlsplit, urlunsplit | ||
| 48 | 44 | import warnings | |
| 49 | - | ||
| 50 | - # NOTE: Unused imports can be improved now that CI testing has fully resumed. Some of | ||
| 51 | - # these be used indirectly through other GitPython modules, which avoids having to write | ||
| 52 | - # gitdb all the time in their imports. They are not in __all__, at least currently, | ||
| 53 | - # because they could be removed or changed at any time, and so should not be considered | ||
| 54 | - # conceptually public to code outside GitPython. Linters of course do not like it. | ||
| 55 | - from gitdb.util import ( | ||
| 56 | - LazyMixin, # noqa: F401 | ||
| 57 | - LockedFD, # noqa: F401 | ||
| 58 | - bin_to_hex, # noqa: F401 | ||
| 59 | - file_contents_ro, # noqa: F401 | ||
| 60 | - file_contents_ro_filepath, # noqa: F401 | ||
| 61 | - hex_to_bin, # noqa: F401 | ||
| 62 | - make_sha, | ||
| 63 | - to_bin_sha, # noqa: F401 | ||
| 64 | - to_hex_sha, # noqa: F401 | ||
| 65 | - ) | ||
| 45 | + from abc import abstractmethod | ||
| 46 | + from functools import wraps | ||
| 47 | + from pathlib import Path | ||
| 66 | 48 | ||
| 67 | 49 | # typing --------------------------------------------------------- | |
| 68 | - | ||
| 69 | 50 | from typing import ( | |
| 51 | + IO, | ||
| 52 | + TYPE_CHECKING, | ||
| 70 | 53 | Any, | |
| 71 | 54 | AnyStr, | |
| 72 | 55 | Callable, | |
| 73 | 56 | Dict, | |
| 74 | 57 | Generator, | |
| 75 | - IO, | ||
| 76 | 58 | Iterator, | |
| 77 | 59 | List, | |
| 78 | 60 | Optional, | |
| 79 | 61 | Pattern, | |
| 80 | 62 | Sequence, | |
| 81 | 63 | Tuple, | |
| 82 | - TYPE_CHECKING, | ||
| 83 | 64 | Type, | |
| 84 | 65 | TypeVar, | |
| 85 | 66 | Union, | |
| 86 | 67 | cast, | |
| 87 | 68 | overload, | |
| 88 | 69 | ) | |
| 70 | + from urllib.parse import urlsplit, urlunsplit | ||
| 71 | + | ||
| 72 | + # NOTE: Unused imports can be improved now that CI testing has fully resumed. Some of | ||
| 73 | + # these be used indirectly through other GitPython modules, which avoids having to write | ||
| 74 | + # gitdb all the time in their imports. They are not in __all__, at least currently, | ||
| 75 | + # because they could be removed or changed at any time, and so should not be considered | ||
| 76 | + # conceptually public to code outside GitPython. Linters of course do not like it. | ||
| 77 | + from gitdb.util import ( | ||
| 78 | + LazyMixin, # noqa: F401 | ||
| 79 | + LockedFD, # noqa: F401 | ||
| 80 | + bin_to_hex, # noqa: F401 | ||
| 81 | + file_contents_ro, # noqa: F401 | ||
| 82 | + file_contents_ro_filepath, # noqa: F401 | ||
| 83 | + hex_to_bin, # noqa: F401 | ||
| 84 | + make_sha, | ||
| 85 | + to_bin_sha, # noqa: F401 | ||
| 86 | + to_hex_sha, # noqa: F401 | ||
| 87 | + ) | ||
| 89 | 88 | ||
| 90 | 89 | if TYPE_CHECKING: | |
| 91 | 90 | from git.cmd import Git | |
@@ -94,9 +93,9 @@ | |||
| 94 | 93 | from git.repo.base import Repo | |
| 95 | 94 | ||
| 96 | 95 | from git.types import ( | |
| 96 | + HSH_TD, | ||
| 97 | 97 | Files_TD, | |
| 98 | 98 | Has_id_attribute, | |
| 99 | - HSH_TD, | ||
| 100 | 99 | Literal, | |
| 101 | 100 | PathLike, | |
| 102 | 101 | Protocol, | |
@@ -385,12 +384,27 @@ def _to_relative_path(root: PathLike, path: PathLike) -> str: | |||
| 385 | 384 | "", "", "\u200c\u200d\u200e\u200f\u202a\u202b\u202c\u202d\u202e\u206a\u206b\u206c\u206d\u206e\u206f\ufeff" | |
| 386 | 385 | ) | |
| 387 | 386 | ||
| 387 | + # Match Git's is_ntfs_dotgitmodules in path.c on a lowercased, trimmed name. | ||
| 388 | + # Besides gitmod~1..4, fallback aliases have exactly eight ASCII characters: | ||
| 389 | + # a shrinking prefix of "gi7eba", "~", and digits with no leading zero. | ||
| 390 | + # Explicit digit counts avoid accepting shorter/longer names or Unicode digits. | ||
| 391 | + _NTFS_DOTGITMODULES_SHORT_NAME = re.compile( | ||
| 392 | + r"(?:gitmod~[1-4]|gi7eba~[1-9]|gi7eb~[1-9][0-9]|gi7e~[1-9][0-9]{2}|" | ||
| 393 | + r"gi7~[1-9][0-9]{3}|gi~[1-9][0-9]{4}|g~[1-9][0-9]{5}|~[1-9][0-9]{6})" | ||
| 394 | + ) | ||
| 395 | + | ||
| 388 | 396 | ||
| 389 | - def _validate_repo_path(path: PathLike) -> None: | ||
| 397 | + def _validate_repo_path(path: PathLike, mode: Union[int, None] = None) -> None: | ||
| 390 | 398 | """Reject unsafe tree/index paths without normalizing away their components. | |
| 391 | 399 | ||
| 392 | 400 | Protect Git metadata aliases on NTFS and HFS even when writing on another | |
| 393 | 401 | platform. Other POSIX filename characters, including newlines, remain valid. | |
| 402 | + | ||
| 403 | + :param mode: | ||
| 404 | + Mode of the index or tree entry the path belongs to, where one is known. | ||
| 405 | + Git refuses a symbolic link that aliases ``.gitmodules``, since the | ||
| 406 | + submodule configuration would then be read through the link, so that | ||
| 407 | + name is only rejected once the mode says the entry is a link. | ||
| 394 | 408 | """ | |
| 395 | 409 | name = os.fspath(path) | |
| 396 | 410 | if not name or "\0" in name or ntpath.splitdrive(name)[0] or name.startswith("/"): | |
@@ -406,6 +420,13 @@ def _validate_repo_path(path: PathLike) -> None: | |||
| 406 | 420 | hfs_name = part.translate(_HFS_IGNORABLES).lower() | |
| 407 | 421 | if ntfs_name in (".git", "git~1") or hfs_name == ".git": | |
| 408 | 422 | raise ValueError("Repository path aliases Git metadata: %r" % name) | |
| 423 | + if mode is not None and stat.S_ISLNK(mode): | ||
| 424 | + if ( | ||
| 425 | + ntfs_name == ".gitmodules" | ||
| 426 | + or hfs_name == ".gitmodules" | ||
| 427 | + or _NTFS_DOTGITMODULES_SHORT_NAME.fullmatch(ntfs_name) is not None | ||
| 428 | + ): | ||
| 429 | + raise ValueError("Symbolic link aliases the submodule configuration: %r" % name) | ||
| 409 | 430 | ||
| 410 | 431 | ||
| 411 | 432 | def assure_directory_exists(path: PathLike, is_file: bool = False) -> bool: | |
| Back | FazBrowse Home | New Git URL |
0 commit comments