diff --git a/docs/changelog.md b/docs/changelog.md index f40a93e3..94b4a2f5 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -16,6 +16,9 @@ See the [Contributing Guide](contributing.md) for details. ### 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 (#1656). * Fix regression in `BacktickInlineProcessor` where text could be lost when opening and closing backticks did not match (#1668). * Escape the language name in the `codehilite` extension's non-Pygments output to diff --git a/markdown/extensions/attr_list.py b/markdown/extensions/attr_list.py index c1643dd1..b3b8d951 100644 --- a/markdown/extensions/attr_list.py +++ b/markdown/extensions/attr_list.py @@ -105,7 +105,7 @@ def sanitize_name(name: str) -> str: 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 1a0ae12c..dabac77d 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 over every way of + splitting 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(