| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent f8776ee commit 2235723
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -334,7 +334,11 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder): | |||
| 334 | 334 | ||
| 335 | 335 | OPTVALUEONLY = re.compile(optvalueonly_source) | |
| 336 | 336 | ||
| 337 | - OPTCRE = re.compile(optvalueonly_source + r"\s*(?P<vi>[:=])\s*" + r"(?P<value>.*)$") | ||
| 337 | + # The option name class [^:=#;]* already consumes any spaces up to the ":" or "=", | ||
| 338 | + # so a second \s* before the indicator would overlap it and backtrack quadratically | ||
| 339 | + # on a line that never reaches an indicator (for example a key followed by a long | ||
| 340 | + # whitespace run). Drop the redundant \s*; the name is right-stripped after parsing. | ||
| 341 | + OPTCRE = re.compile(optvalueonly_source + r"(?P<vi>[:=])\s*" + r"(?P<value>.*)$") | ||
| 338 | 342 | ||
| 339 | 343 | del optvalueonly_source | |
| 340 | 344 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,6 +9,7 @@ | |||
| 9 | 9 | import os.path as osp | |
| 10 | 10 | import subprocess | |
| 11 | 11 | import sys | |
| 12 | + import time | ||
| 12 | 13 | from unittest import mock | |
| 13 | 14 | ||
| 14 | 15 | import pytest | |
@@ -262,6 +263,25 @@ def test_inline_comments_are_stripped_like_git(self): | |||
| 262 | 263 | with self.subTest(content=content): | |
| 263 | 264 | self.assertEqual(config.get_value("a", "k"), expected) | |
| 264 | 265 | ||
| 266 | + def test_option_line_with_long_whitespace_run_is_not_quadratic(self): | ||
| 267 | + """A key followed by a long whitespace run and no indicator must not make | ||
| 268 | + the option regex backtrack quadratically. | ||
| 269 | + | ||
| 270 | + `.gitmodules` and other config files are fully controlled by any repository | ||
| 271 | + that is inspected, so a crafted line must stay cheap to parse. Keys that come | ||
| 272 | + before the malformed line are still read. | ||
| 273 | + """ | ||
| 274 | + malformed = b'[submodule "x"]\n\tpath = x\n\tbranch' + b" " * 200_000 + b"\n" | ||
| 275 | + config_file = io.BytesIO(malformed) | ||
| 276 | + config_file.name = ".gitmodules" | ||
| 277 | + config = GitConfigParser(config_file) | ||
| 278 | + start = time.process_time() | ||
| 279 | + config.read() | ||
| 280 | + elapsed = time.process_time() - start | ||
| 281 | + # Leave ample CPU time for slow runners, but catch quadratic backtracking. | ||
| 282 | + self.assertLess(elapsed, 1.0) | ||
| 283 | + self.assertEqual(config.get_value('submodule "x"', "path"), "x") | ||
| 284 | + | ||
| 265 | 285 | @with_rw_directory | |
| 266 | 286 | def test_inline_comments_preserve_balanced_quotes_and_following_settings(self, rw_dir): | |
| 267 | 287 | config_path = osp.join(rw_dir, "config") | |
| Back | FazBrowse Home | New Git URL |
0 commit comments