diff --git a/docs/changelog.md b/docs/changelog.md index 91373276..2e8d9bf8 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -26,6 +26,9 @@ See the [Contributing Guide](contributing.md) for details. * Fix an issue with excessive backtracking when matching inline code blocks (#1617). * `md_in_html` now honors tags added to `Markdown.block_level_elements` after the extension is loaded (#1246). +* Fix quadratic-time regex backtracking in `ReferenceProcessor` when a link + reference definition has no URL, e.g. a line consisting only of `[id]:` + followed by many trailing spaces (#798). ## [3.10.3] - 2026-07-30 diff --git a/markdown/blockprocessors.py b/markdown/blockprocessors.py index c2d20ddb..cbad917b 100644 --- a/markdown/blockprocessors.py +++ b/markdown/blockprocessors.py @@ -577,7 +577,8 @@ def run(self, parent: etree.Element, blocks: list[str]) -> None: class ReferenceProcessor(BlockProcessor): """ Process link references. """ RE = re.compile( - r'^[ ]{0,3}\[([^\[\]]*)\]:[ ]*\n?[ ]*([^\s]+)[ ]*(?:\n[ ]*)?((["\'])(.*)\4[ ]*|\((.*)\)[ ]*)?$', re.MULTILINE + r'^[ ]{0,3}\[([^\[\]]*)\]:[ ]*(?:\n[ ]*)?([^\s]+)[ ]*(?:\n[ ]*)?((["\'])(.*)\4[ ]*|\((.*)\)[ ]*)?$', + re.MULTILINE ) def test(self, parent: etree.Element, block: str) -> bool: diff --git a/tests/test_syntax/blocks/test_reference_links.py b/tests/test_syntax/blocks/test_reference_links.py new file mode 100644 index 00000000..8bd67521 --- /dev/null +++ b/tests/test_syntax/blocks/test_reference_links.py @@ -0,0 +1,62 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2023 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +from markdown.test_tools import TestCase + + +class TestReferenceLinks(TestCase): + + def test_reference_link(self): + self.assertMarkdownRenders( + '[Text][id]\n\n[id]: http://example.com', + '

Text

' + ) + + def test_reference_link_split_across_lines(self): + self.assertMarkdownRenders( + '[Text][id]\n\n[id]:\nhttp://example.com', + '

Text

' + ) + + def test_reference_link_with_title(self): + self.assertMarkdownRenders( + '[Text][id]\n\n[id]: http://example.com "Title"', + '

Text

' + ) + + def test_reference_link_title_on_own_line(self): + self.assertMarkdownRenders( + '[Text][id]\n\n[id]: http://example.com\n"Title"', + '

Text

' + ) + + def test_malformed_reference_with_long_run_of_spaces(self): + """ + A reference definition whose URL is missing (only trailing spaces + after the colon) should still be treated as plain text, no matter + how many trailing spaces there are. + + See https://github.com/Python-Markdown/markdown/issues/798 + """ + text = '[id]:' + (' ' * 50000) + self.assertMarkdownRenders( + text, f'

{text}

', expected_attrs={'references': {}} + )