From 77b3ac16da686443fdd4e3887b98db2a2776d4eb Mon Sep 17 00:00:00 2001 From: Rahul Ranjan <11793455+rahul05ranjan@users.noreply.github.com> Date: Thu, 29 Oct 2026 05:18:13 +0530 Subject: [PATCH] fix: don't link-pattern matches inside inline HTML markup LinkPatterns.run guards its matches against markdown link syntax and auto-links, but not against the other tokens that markdown2's own tokenizer (_sorta_html_tokenize_re) recognizes as inline HTML markup: tags, comments, processing instructions and style auto-link tokens. As a result the extra replaces matches inside markup, corrupting output: - baz became - foo got an anchor inside its alt attribute - lost its mailto auto-link entirely: the match split the token so the auto-linker no longer recognized it, leaving broken text behind Compute the markup token spans from the same tokenizer (respecting the escape-prefix rule from _escape_special_chars) per pattern and skip matches that fall inside them. Matches in plain prose are unaffected. Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com> --- lib/markdown2.py | 16 ++++++++++++++++ test/tm-cases/link_patterns_email_autolink.html | 1 + test/tm-cases/link_patterns_email_autolink.opts | 4 ++++ test/tm-cases/link_patterns_email_autolink.tags | 1 + test/tm-cases/link_patterns_email_autolink.text | 1 + test/tm-cases/link_patterns_html_tags.html | 7 +++++++ test/tm-cases/link_patterns_html_tags.opts | 4 ++++ test/tm-cases/link_patterns_html_tags.tags | 1 + test/tm-cases/link_patterns_html_tags.text | 7 +++++++ 9 files changed, 42 insertions(+) create mode 100644 test/tm-cases/link_patterns_email_autolink.html create mode 100644 test/tm-cases/link_patterns_email_autolink.opts create mode 100644 test/tm-cases/link_patterns_email_autolink.tags create mode 100644 test/tm-cases/link_patterns_email_autolink.text create mode 100644 test/tm-cases/link_patterns_html_tags.html create mode 100644 test/tm-cases/link_patterns_html_tags.opts create mode 100644 test/tm-cases/link_patterns_html_tags.tags create mode 100644 test/tm-cases/link_patterns_html_tags.text diff --git a/lib/markdown2.py b/lib/markdown2.py index 3632a54e..1ac1160d 100755 --- a/lib/markdown2.py +++ b/lib/markdown2.py @@ -3861,11 +3861,27 @@ class LinkPatterns(Extra): def run(self, text: str): link_from_hash = {} for regex, repl in self.options: + # Spans of inline HTML markup in the current text: tags, + # comments, processing instructions and `<...>` auto-link + # tokens (as recognized by the same tokenizer that + # `_escape_special_chars` uses). Matches inside them must + # not be linkified: that is markup, not prose, and replacing + # inside it corrupts attributes and destroys auto-links + # (e.g. ``, `word`). + markup_spans = [ + m.span(1) + for m in self.md._sorta_html_tokenize_re.finditer(text) + if self.md._is_unescaped_re.match(m.group(1)) + ] replacements = [] for match in regex.finditer(text): if any(self.md._match_overlaps_substr(text, match, h) for h in link_from_hash): continue + # Do not match inside inline HTML markup. + if any(s <= match.start() and match.end() <= e for s, e in markup_spans): + continue + if callable(repl): href = repl(match) else: diff --git a/test/tm-cases/link_patterns_email_autolink.html b/test/tm-cases/link_patterns_email_autolink.html new file mode 100644 index 00000000..1747d494 --- /dev/null +++ b/test/tm-cases/link_patterns_email_autolink.html @@ -0,0 +1 @@ +

foo@example.com is an auto-linked email, but plain foo is still linkified.

diff --git a/test/tm-cases/link_patterns_email_autolink.opts b/test/tm-cases/link_patterns_email_autolink.opts new file mode 100644 index 00000000..02c491dc --- /dev/null +++ b/test/tm-cases/link_patterns_email_autolink.opts @@ -0,0 +1,4 @@ +{"extras": ["link-patterns"], + "link_patterns": [ + (re.compile(r"\b(foo)\b"), r"/w/\1"), + ]} diff --git a/test/tm-cases/link_patterns_email_autolink.tags b/test/tm-cases/link_patterns_email_autolink.tags new file mode 100644 index 00000000..9e0524dd --- /dev/null +++ b/test/tm-cases/link_patterns_email_autolink.tags @@ -0,0 +1 @@ +link_patterns diff --git a/test/tm-cases/link_patterns_email_autolink.text b/test/tm-cases/link_patterns_email_autolink.text new file mode 100644 index 00000000..2ea48b35 --- /dev/null +++ b/test/tm-cases/link_patterns_email_autolink.text @@ -0,0 +1 @@ + is an auto-linked email, but plain foo is still linkified. diff --git a/test/tm-cases/link_patterns_html_tags.html b/test/tm-cases/link_patterns_html_tags.html new file mode 100644 index 00000000..1d6760e7 --- /dev/null +++ b/test/tm-cases/link_patterns_html_tags.html @@ -0,0 +1,7 @@ +

Inline HTML attributes must not be linkified:

+ +

baz

+ +

foo

+ +

But plain foo in prose is linkified.

diff --git a/test/tm-cases/link_patterns_html_tags.opts b/test/tm-cases/link_patterns_html_tags.opts new file mode 100644 index 00000000..02c491dc --- /dev/null +++ b/test/tm-cases/link_patterns_html_tags.opts @@ -0,0 +1,4 @@ +{"extras": ["link-patterns"], + "link_patterns": [ + (re.compile(r"\b(foo)\b"), r"/w/\1"), + ]} diff --git a/test/tm-cases/link_patterns_html_tags.tags b/test/tm-cases/link_patterns_html_tags.tags new file mode 100644 index 00000000..9e0524dd --- /dev/null +++ b/test/tm-cases/link_patterns_html_tags.tags @@ -0,0 +1 @@ +link_patterns diff --git a/test/tm-cases/link_patterns_html_tags.text b/test/tm-cases/link_patterns_html_tags.text new file mode 100644 index 00000000..326906e8 --- /dev/null +++ b/test/tm-cases/link_patterns_html_tags.text @@ -0,0 +1,7 @@ +Inline HTML attributes must not be linkified: + +baz + +foo + +But plain foo in prose is linkified.