diff --git a/docs/changelog.md b/docs/changelog.md index 4f5c00729..1e2f9dbd8 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -12,6 +12,14 @@ and this project adheres to the [Python Version Specification](https://packaging.python.org/en/latest/specifications/version-specifiers/). See the [Contributing Guide](contributing.md) for details. +## [Unreleased] + +### Fixed + +* Fix quadratic-time regex backtracking in `attr_list` when a block ends with an + unclosed attribute list followed by a long run of spaces, e.g. a line + consisting only of `{` and some text with many trailing spaces. + ## [3.11.1] - 2026-10-13 ### Fixed diff --git a/markdown/extensions/attr_list.py b/markdown/extensions/attr_list.py index 518288087..f1edc5798 100644 --- a/markdown/extensions/attr_list.py +++ b/markdown/extensions/attr_list.py @@ -90,7 +90,7 @@ def isheader(elem: Element) -> bool: class AttrListTreeprocessor(Treeprocessor): - BASE_RE = r'\{\:?[ ]*([^\}\n ][^\n]*)[ ]*\}' + BASE_RE = r'\{\:?[ ]*([^\}\n ][^\n]*)\}' HEADER_RE = re.compile(r'[ ]+{}[ ]*$'.format(BASE_RE)) BLOCK_RE = re.compile(r'\n[ ]*{}[ ]*$'.format(BASE_RE)) INLINE_RE = re.compile(r'^{}'.format(BASE_RE)) diff --git a/tests/test_syntax/extensions/test_attr_list.py b/tests/test_syntax/extensions/test_attr_list.py index 1a0ae12c2..7558d0503 100644 --- a/tests/test_syntax/extensions/test_attr_list.py +++ b/tests/test_syntax/extensions/test_attr_list.py @@ -78,6 +78,18 @@ def test_backslash_escape_value(self): '

*Foo*

' ) + def test_unclosed_curly_with_long_run_of_spaces(self): + """ + An unclosed attribute list followed by a long run of spaces should be + left as plain text, no matter how many trailing spaces there are. + + The content and the trailing spaces both match the same part of the + pattern, so without care the regex backtracks quadratically over the + spaces while failing to find the closing brace. + """ + text = 'foo\n{a' + (' ' * 50000) + self.assertMarkdownRenders(text, f'

{text}

') + def test_table_td(self): self.assertMarkdownRenders( self.dedent(