| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -106,7 +106,16 @@ def _set_cache_(self, attr: str) -> None: | |||
| 106 | 106 | """Cache all our attributes at once.""" | |
| 107 | 107 | if attr in TagObject.__slots__: | |
| 108 | 108 | ostream = self.repo.odb.stream(self.binsha) | |
| 109 | - lines: List[str] = ostream.read().decode(defenc, "replace").splitlines() | ||
| 109 | + # Git ends a header only at a line feed, but str.splitlines() also | ||
| 110 | + # breaks at "\r", "\v", "\f", U+2028 and similar. Splitting a tag | ||
| 111 | + # object that way lets an untrusted "tag" header containing such a | ||
| 112 | + # character shift every later header by a line. Split at "\n" only, | ||
| 113 | + # and drop the trailing empty element so tags that use line feeds | ||
| 114 | + # parse exactly as before. | ||
| 115 | + data = ostream.read().decode(defenc, "replace") | ||
| 116 | + lines: List[str] = data.split("\n") | ||
| 117 | + if lines and lines[-1] == "": | ||
| 118 | + lines.pop() | ||
| 110 | 119 | ||
| 111 | 120 | _obj, hexsha = lines[0].split(" ") | |
| 112 | 121 | _type_token, type_name = lines[1].split(" ") | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,10 +7,12 @@ | |||
| 7 | 7 | import os.path as osp | |
| 8 | 8 | import tempfile | |
| 9 | 9 | import unittest | |
| 10 | + from io import BytesIO | ||
| 10 | 11 | from itertools import chain | |
| 11 | 12 | from pathlib import Path | |
| 12 | 13 | from unittest.mock import patch | |
| 13 | 14 | ||
| 15 | + from gitdb import IStream | ||
| 14 | 16 | from gitdb.exc import BadName | |
| 15 | 17 | ||
| 16 | 18 | import git.refs as refs | |
@@ -139,6 +141,31 @@ def test_tags_author(self): | |||
| 139 | 141 | tagger_name = tagobj.tagger.name | |
| 140 | 142 | assert tagger_name == "Michael Trier" | |
| 141 | 143 | ||
| 144 | + def test_tag_headers_split_at_line_feeds_only(self): | ||
| 145 | + # A "tag" header value carrying a U+2028 (or any separator that | ||
| 146 | + # str.splitlines() honors but git does not) must not shift the later | ||
| 147 | + # headers. Git ends a header only at "\n", so GitPython must read the | ||
| 148 | + # whole value as the tag name and the following line as the tagger, | ||
| 149 | + # exactly as "git cat-file tag" does. See the TagObject parsing fix. | ||
| 150 | + with tempfile.TemporaryDirectory() as tmp_dir: | ||
| 151 | + with self._repo_with_initial_commit(Path(tmp_dir)) as repo: | ||
| 152 | + commit_hexsha = repo.head.commit.hexsha | ||
| 153 | + payload = ( | ||
| 154 | + f"object {commit_hexsha}\n" | ||
| 155 | + "type commit\n" | ||
| 156 | + "tag v1
tagger Linus Torvalds <torvalds@linux-foundation.org> 1700000000 +0000\n" | ||
| 157 | + "tagger Mallory <m@evil.example> 1700000001 +0000\n" | ||
| 158 | + "\n" | ||
| 159 | + "release\n" | ||
| 160 | + ).encode("utf-8") | ||
| 161 | + istream = repo.odb.store(IStream("tag", len(payload), BytesIO(payload))) | ||
| 162 | + tagobj = TagObject(repo, istream.binsha) | ||
| 163 | + | ||
| 164 | + assert tagobj.tag == "v1
tagger Linus Torvalds <torvalds@linux-foundation.org> 1700000000 +0000" | ||
| 165 | + assert tagobj.tagger.name == "Mallory" | ||
| 166 | + assert tagobj.tagger.email == "m@evil.example" | ||
| 167 | + assert tagobj.message == "release" | ||
| 168 | + | ||
| 142 | 169 | def test_tags(self): | |
| 143 | 170 | # Tag refs can point to tag objects or to commits. | |
| 144 | 171 | s = set() | |
| Back | FazBrowse Home | New Git URL |
0 commit comments