diff --git a/CHANGES.md b/CHANGES.md index 90f815ec..49c1cc35 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,7 @@ - [pull #734] Fix empty list items being parsed as nested lists (#167) - [pull #735] Fix `smarty-pants` extra converting quotes inside inline HTML tags and comments (#150) - [pull #737] Fix XSS from code spans that the span gamut doesn't treat as code spans in safe mode (#726) +- [pull #743] Add a `strip-syntax` option to the `header-ids` extra that leaves link URLs, image paths, inline HTML tags and emphasis markers out of header ids (#192) ## python-markdown2 2.5.5 diff --git a/lib/markdown2.py b/lib/markdown2.py index 66d49a69..a494b286 100755 --- a/lib/markdown2.py +++ b/lib/markdown2.py @@ -56,7 +56,9 @@ * footnotes: Support footnotes as in use on daringfireball.net and implemented in other Markdown processors (tho not in Markdown.pl v1.0.1). * header-ids: Adds "id" attributes to headers. The id value is a slug of - the header text. + the header text. With `{"strip-syntax": True}`, the slug is made from the + rendered header, so link URLs, image paths, inline HTML tags and emphasis + markers are left out of it. * highlightjs-lang: Allows specifying the language which used for syntax highlighting when using fenced-code-blocks and highlightjs. * html-classes: Takes a dict mapping html tag names (lowercase) to a @@ -1661,6 +1663,8 @@ def _do_links(self, text: str) -> str: text = link_processor.run(text) return text + _html_tag_re = re.compile(r'<[^>]*>') + def header_id_from_text(self, text: str, prefix: str, @@ -1745,16 +1749,26 @@ def _h_sub(self, match: re.Match[str]) -> str: if demote_headers: n = min(n + demote_headers, 6) header_id_attr = "" + html = self._run_span_gamut(header_group) if "header-ids" in self.extras: # in safe mode, inline HTML is hashed by now. Unhash it so the # sanitized HTML doesn't get swapped back into the id attribute - header_text = self._unhash_html_spans(header_group) + if self.extras["header-ids"].get('strip-syntax'): + # build the id from the rendered header so that markdown syntax + # (link URLs, image paths, emphasis markers) doesn't end up in it. + header_text = self._html_tag_re.sub('', self._unhash_html_spans(html)) + if "footnotes" in self.extras: + # footnote refs are numbered later on. Drop the placeholder + # so it doesn't end up in the id + marker = self._footnote_marker + '-' + header_text = header_text.replace(marker, '') + else: + header_text = self._unhash_html_spans(header_group) header_id = self.header_id_from_text( self._unescape_special_chars(header_text), self.extras["header-ids"].get('prefix'), n) if header_id: header_id_attr = ' id="%s"' % header_id - html = self._run_span_gamut(header_group) if "toc" in self.extras and header_id: self._toc_add_entry(n, header_id, html) return "%s\n\n" % (n, header_id_attr, html, n) diff --git a/test/tm-cases/header_ids_footnote_ref.html b/test/tm-cases/header_ids_footnote_ref.html new file mode 100644 index 00000000..1489d32f --- /dev/null +++ b/test/tm-cases/header_ids_footnote_ref.html @@ -0,0 +1,16 @@ +

Header with a footnote1

+ +

Linked header2

+ +
+
+
    +
  1. +

    The first note. ↩

    +
  2. + +
  3. +

    The second note. ↩

    +
  4. +
+
diff --git a/test/tm-cases/header_ids_footnote_ref.opts b/test/tm-cases/header_ids_footnote_ref.opts new file mode 100644 index 00000000..fa2d42bf --- /dev/null +++ b/test/tm-cases/header_ids_footnote_ref.opts @@ -0,0 +1 @@ +{"extras": {"header-ids": {"strip-syntax": True}, "footnotes": None}} diff --git a/test/tm-cases/header_ids_footnote_ref.tags b/test/tm-cases/header_ids_footnote_ref.tags new file mode 100644 index 00000000..51fe5df8 --- /dev/null +++ b/test/tm-cases/header_ids_footnote_ref.tags @@ -0,0 +1 @@ +extra header-ids footnotes issue192 diff --git a/test/tm-cases/header_ids_footnote_ref.text b/test/tm-cases/header_ids_footnote_ref.text new file mode 100644 index 00000000..39a50b8c --- /dev/null +++ b/test/tm-cases/header_ids_footnote_ref.text @@ -0,0 +1,6 @@ +# Header with a footnote[^1] + +# [Linked](http://example.com) header[^note] + +[^1]: The first note. +[^note]: The second note. diff --git a/test/tm-cases/header_ids_footnote_ref_default.html b/test/tm-cases/header_ids_footnote_ref_default.html new file mode 100644 index 00000000..7c15fb22 --- /dev/null +++ b/test/tm-cases/header_ids_footnote_ref_default.html @@ -0,0 +1,16 @@ +

Header with a footnote1

+ +

Linked header2

+ +
+
+
    +
  1. +

    The first note. ↩

    +
  2. + +
  3. +

    The second note. ↩

    +
  4. +
+
diff --git a/test/tm-cases/header_ids_footnote_ref_default.opts b/test/tm-cases/header_ids_footnote_ref_default.opts new file mode 100644 index 00000000..00b84fb2 --- /dev/null +++ b/test/tm-cases/header_ids_footnote_ref_default.opts @@ -0,0 +1 @@ +{"extras": ["header-ids", "footnotes"]} diff --git a/test/tm-cases/header_ids_footnote_ref_default.tags b/test/tm-cases/header_ids_footnote_ref_default.tags new file mode 100644 index 00000000..51fe5df8 --- /dev/null +++ b/test/tm-cases/header_ids_footnote_ref_default.tags @@ -0,0 +1 @@ +extra header-ids footnotes issue192 diff --git a/test/tm-cases/header_ids_footnote_ref_default.text b/test/tm-cases/header_ids_footnote_ref_default.text new file mode 100644 index 00000000..39a50b8c --- /dev/null +++ b/test/tm-cases/header_ids_footnote_ref_default.text @@ -0,0 +1,6 @@ +# Header with a footnote[^1] + +# [Linked](http://example.com) header[^note] + +[^1]: The first note. +[^note]: The second note. diff --git a/test/tm-cases/header_ids_issue192.html b/test/tm-cases/header_ids_issue192.html new file mode 100644 index 00000000..bec35661 --- /dev/null +++ b/test/tm-cases/header_ids_issue192.html @@ -0,0 +1,13 @@ +

Inline link

+ +

Reference link

+ +

logo Image

+ +

Bold link

+ +

Inline HTML

+ +

Strong and em

+ +

code_span stays

diff --git a/test/tm-cases/header_ids_issue192.opts b/test/tm-cases/header_ids_issue192.opts new file mode 100644 index 00000000..b71e6519 --- /dev/null +++ b/test/tm-cases/header_ids_issue192.opts @@ -0,0 +1 @@ +{"extras": {"header-ids": {"strip-syntax": True}}} diff --git a/test/tm-cases/header_ids_issue192.tags b/test/tm-cases/header_ids_issue192.tags new file mode 100644 index 00000000..d01b19bc --- /dev/null +++ b/test/tm-cases/header_ids_issue192.tags @@ -0,0 +1 @@ +extra header-ids issue192 diff --git a/test/tm-cases/header_ids_issue192.text b/test/tm-cases/header_ids_issue192.text new file mode 100644 index 00000000..4ab848ae --- /dev/null +++ b/test/tm-cases/header_ids_issue192.text @@ -0,0 +1,15 @@ +# [Inline link](https://github.com) + +# [Reference link][ref] + +# ![logo](logo.png) Image + +# **[Bold link](http://example.com "Title")** + +# Inline HTML + +# __Strong__ and _em_ + +# `code_span` stays + +[ref]: http://example.com diff --git a/test/tm-cases/header_ids_issue192_default.html b/test/tm-cases/header_ids_issue192_default.html new file mode 100644 index 00000000..20c66497 --- /dev/null +++ b/test/tm-cases/header_ids_issue192_default.html @@ -0,0 +1,13 @@ +

Inline link

+ +

Reference link

+ +

logo Image

+ +

Bold link

+ +

Inline HTML

+ +

Strong and em

+ +

code_span stays

diff --git a/test/tm-cases/header_ids_issue192_default.opts b/test/tm-cases/header_ids_issue192_default.opts new file mode 100644 index 00000000..38b8bfe8 --- /dev/null +++ b/test/tm-cases/header_ids_issue192_default.opts @@ -0,0 +1 @@ +{"extras": ["header-ids"]} diff --git a/test/tm-cases/header_ids_issue192_default.tags b/test/tm-cases/header_ids_issue192_default.tags new file mode 100644 index 00000000..d01b19bc --- /dev/null +++ b/test/tm-cases/header_ids_issue192_default.tags @@ -0,0 +1 @@ +extra header-ids issue192 diff --git a/test/tm-cases/header_ids_issue192_default.text b/test/tm-cases/header_ids_issue192_default.text new file mode 100644 index 00000000..4ab848ae --- /dev/null +++ b/test/tm-cases/header_ids_issue192_default.text @@ -0,0 +1,15 @@ +# [Inline link](https://github.com) + +# [Reference link][ref] + +# ![logo](logo.png) Image + +# **[Bold link](http://example.com "Title")** + +# Inline HTML + +# __Strong__ and _em_ + +# `code_span` stays + +[ref]: http://example.com