|
4 | 4 |
|
5 | 5 | from __future__ import annotations |
6 | 6 |
|
| 7 | +import sys |
7 | 8 | from types import MappingProxyType |
8 | 9 |
|
9 | 10 | from ._re import ( |
|
18 | 19 | TYPE_CHECKING = False |
19 | 20 | if TYPE_CHECKING: |
20 | 21 | from collections.abc import Iterable |
21 | | - from typing import IO, Any |
| 22 | + from typing import IO, Any, Final |
22 | 23 |
|
23 | 24 | from ._types import Key, ParseFloat, Pos |
24 | 25 |
|
| 26 | +# Pathologically excessive number of parts in a key runs into quadratic |
| 27 | +# behavior (e.g. in Flags.is_). |
| 28 | +# Even if keys aren't currently parsed using recursion, they name a |
| 29 | +# recursive structure, so it makes sense to limit it using getrecursionlimit() |
| 30 | +# and RecursionError. |
| 31 | +MAX_KEY_PARTS: Final = sys.getrecursionlimit() |
| 32 | + |
25 | 33 | ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127)) |
26 | 34 |
|
27 | 35 | # Neither of these sets include quotation mark or backslash. They are |
@@ -462,6 +470,10 @@ def parse_key(src: str, pos: Pos) -> tuple[Pos, Key]: |
462 | 470 | pos = skip_chars(src, pos, TOML_WS) |
463 | 471 | pos, key_part = parse_key_part(src, pos) |
464 | 472 | key += (key_part,) |
| 473 | + if len(key) > MAX_KEY_PARTS: |
| 474 | + raise RecursionError( |
| 475 | + f"TOML key has more than the allowed {MAX_KEY_PARTS} parts" |
| 476 | + ) |
465 | 477 | pos = skip_chars(src, pos, TOML_WS) |
466 | 478 |
|
467 | 479 |
|
|
0 commit comments