Skip to content

Commit 71b9545

Browse files
authored
Merge pull request #2253 from gitpython-developers/fix-regex
fix: bound actor and date parsing
2 parents 153cc76 + 8290c98 commit 71b9545

3 files changed

Lines changed: 54 additions & 3 deletions

File tree

‎doc/source/changes.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Changelog
88
Security fixes for
99

1010
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-w8jc-g24h-crhw
11+
* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-m64x-33q8-m5h7
1112

1213
3.2.0
1314
=====

‎git/objects/util.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,10 @@ def parse_date(string_date: Union[str, datetime]) -> Tuple[int, int]:
318318
# END handle exceptions
319319

320320

321-
# Precompiled regexes
322-
_re_actor_epoch = re.compile(r"^.+? (.*) (\d+) ([+-]\d+).*$")
323-
_re_only_actor = re.compile(r"^.+? (.*)$")
321+
# Check the line ending once, before parsing fields, to avoid repeated backtracking.
322+
# Keep the field name from consuming spaces belonging to the actor.
323+
_re_actor_epoch = re.compile(r"^(?=[^\n]*$).[^ \n]* (.*) (\d+) ([+-]\d+)")
324+
_re_only_actor = re.compile(r"^.[^ \n]* (.*)$")
324325

325326

326327
def parse_actor_and_date(line: str) -> Tuple[Actor, int, int]:

‎test/test_util.py‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from git.objects.util import (
2323
altz_to_utctz_str,
2424
from_timestamp,
25+
parse_actor_and_date,
2526
parse_date,
2627
tzoffset,
2728
utctz_to_altz,
@@ -571,6 +572,54 @@ def test_actor_from_string(self):
571572
Actor("name last another", "some-very-long-email@example.com"),
572573
)
573574

575+
@ddt.data(
576+
("", Actor("", None), 0, 0),
577+
("author", Actor("author", None), 0, 0),
578+
("author Name <email> 42 -0700", Actor("Name", "email"), 42, 25200),
579+
("committer Name <email> 42 +0530\n", Actor("Name", "email"), 42, -19800),
580+
("tagger Name <email> 42 +0000\r\n", Actor("Name", "email"), 42, 0),
581+
("author Name <email> 42 -0700 trailing", Actor("Name", "email"), 42, 25200),
582+
("author Name <email> 1 +0 42 -0700", Actor("Name", "email"), 42, 25200),
583+
("author Name <email> invalid -0700", Actor("Name", "email"), 0, 0),
584+
("author Name <email> 42 invalid", Actor("Name", "email"), 0, 0),
585+
("author 42 -0700", Actor("42 -0700", None), 0, 0),
586+
("author 42 -0700", Actor("", None), 42, 25200),
587+
(" author Name <email> 42 -0700", Actor("Name", "email"), 42, 25200),
588+
("author Näme <email> ١ +٠١٣٠", Actor("Näme", "email"), 1, -5400),
589+
("author\nName <email> 42 -0700", Actor("author\nName <email> 42 -0700", None), 0, 0),
590+
("author Name <email> 42 -0700\nextra", Actor("author Name", "email"), 0, 0),
591+
)
592+
@ddt.unpack
593+
def test_parse_actor_and_date(self, line, actor, epoch, offset):
594+
self.assertEqual(parse_actor_and_date(line), (actor, epoch, offset))
595+
596+
def test_parse_actor_and_date_long_malformed_lines(self):
597+
padding = " " * 64_000
598+
for field in ("author", "committer", "tagger"):
599+
for tail in ("", "<unterminated", "invalid -0700", "42", "-0700", "42 invalid", "42 +"):
600+
actor_text = padding + tail
601+
start = time.process_time()
602+
result = parse_actor_and_date(f"{field} {actor_text}")
603+
elapsed = time.process_time() - start
604+
# Leave ample CPU time for slow runners, but catch excessive backtracking.
605+
self.assertLess(elapsed, 1.0, (field, tail))
606+
self.assertEqual(result, (Actor(actor_text, None), 0, 0))
607+
608+
name = "Long name " * 6_400
609+
self.assertEqual(
610+
parse_actor_and_date(f"{field} {name}<email> 42 -0700"),
611+
(Actor(name.rstrip(), "email"), 42, 25200),
612+
)
613+
614+
def test_parse_actor_and_date_long_multiline_input(self):
615+
for field in ("author", "committer", "tagger"):
616+
line = f"{field} Name <email> 42 +" + "0" * 64_000 + "\nextra"
617+
start = time.process_time()
618+
result = parse_actor_and_date(line)
619+
elapsed = time.process_time() - start
620+
self.assertLess(elapsed, 1.0, field)
621+
self.assertEqual(result, (Actor(f"{field} Name", "email"), 0, 0))
622+
574623
@ddt.data(
575624
("name", ""),
576625
("name", "prefix_"),

0 commit comments

Comments
 (0)