Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-markdown2 for
openSUSE:Factory checked in at 2025-09-22 16:39:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-markdown2 (Old)
and /work/SRC/openSUSE:Factory/.python-markdown2.new.27445 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-markdown2"
Mon Sep 22 16:39:44 2025 rev:18 rq:1306359 version:2.5.4
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-markdown2/python-markdown2.changes
2025-02-18 19:13:57.576678644 +0100
+++
/work/SRC/openSUSE:Factory/.python-markdown2.new.27445/python-markdown2.changes
2025-09-22 16:40:33.711635166 +0200
@@ -1,0 +2,13 @@
+Sun Sep 21 19:34:41 UTC 2025 - Dirk Müller <[email protected]>
+
+- update to 2.5.4:
+ * [pull #617] Add MarkdownFileLinks extra
+ * [pull #622] Add missing block tags to regex
+ * [pull #623] Don't escape plus signs in URLs
+ * [pull #626] Fix XSS when encoding incomplete tags
+ * [pull #628] Fix TypeError in MiddleWordEm extra when options
+ was None
+ * [pull #630] Fix nbsp breaking tables
+ * [pull #634] Fix ReDoS in HTML tokenizer regex
+
+-------------------------------------------------------------------
Old:
----
markdown2-2.5.3.tar.gz
New:
----
markdown2-2.5.4.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-markdown2.spec ++++++
--- /var/tmp/diff_new_pack.BplFV3/_old 2025-09-22 16:40:34.399664074 +0200
+++ /var/tmp/diff_new_pack.BplFV3/_new 2025-09-22 16:40:34.399664074 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-markdown2
#
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2025 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-markdown2
-Version: 2.5.3
+Version: 2.5.4
Release: 0
Summary: A Python implementation of Markdown
License: MIT
++++++ markdown2-2.5.3.tar.gz -> markdown2-2.5.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/markdown2-2.5.3/CHANGES.md
new/markdown2-2.5.4/CHANGES.md
--- old/markdown2-2.5.3/CHANGES.md 2025-01-24 22:13:35.000000000 +0100
+++ new/markdown2-2.5.4/CHANGES.md 2025-07-27 18:16:14.000000000 +0200
@@ -1,5 +1,16 @@
# python-markdown2 Changelog
+## python-markdown2 2.5.4
+
+- [pull #617] Add MarkdownFileLinks extra (#528)
+- [pull #622] Add missing block tags to regex (#620)
+- [pull #623] Don't escape plus signs in URLs (#621)
+- [pull #626] Fix XSS when encoding incomplete tags (#625)
+- [pull #628] Fix TypeError in MiddleWordEm extra when options was None (#627)
+- [pull #630] Fix nbsp breaking tables (#629)
+- [pull #634] Fix ReDoS in HTML tokenizer regex (#633)
+
+
## python-markdown2 2.5.3
- [pull #616] make tables without body gfm compatible
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/markdown2-2.5.3/Makefile new/markdown2-2.5.4/Makefile
--- old/markdown2-2.5.3/Makefile 2023-02-12 23:32:18.000000000 +0100
+++ new/markdown2-2.5.4/Makefile 2025-07-27 18:14:04.000000000 +0200
@@ -12,6 +12,10 @@
testone:
cd test && python test.py -- -knownfailure
+.PHONY: testredos
+testredos:
+ python test/test_redos.py
+
.PHONY: pygments
pygments:
[[ -d deps/pygments ]] || ( \
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/markdown2-2.5.3/PKG-INFO new/markdown2-2.5.4/PKG-INFO
--- old/markdown2-2.5.3/PKG-INFO 2025-01-24 22:13:43.557940000 +0100
+++ new/markdown2-2.5.4/PKG-INFO 2025-07-27 18:16:21.453108500 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: markdown2
-Version: 2.5.3
+Version: 2.5.4
Summary: A fast and complete Python implementation of Markdown
Home-page: https://github.com/trentm/python-markdown2
Author: Trent Mick
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/markdown2-2.5.3/lib/markdown2.egg-info/PKG-INFO
new/markdown2-2.5.4/lib/markdown2.egg-info/PKG-INFO
--- old/markdown2-2.5.3/lib/markdown2.egg-info/PKG-INFO 2025-01-24
22:13:43.000000000 +0100
+++ new/markdown2-2.5.4/lib/markdown2.egg-info/PKG-INFO 2025-07-27
18:16:21.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: markdown2
-Version: 2.5.3
+Version: 2.5.4
Summary: A fast and complete Python implementation of Markdown
Home-page: https://github.com/trentm/python-markdown2
Author: Trent Mick
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/markdown2-2.5.3/lib/markdown2.egg-info/SOURCES.txt
new/markdown2-2.5.4/lib/markdown2.egg-info/SOURCES.txt
--- old/markdown2-2.5.3/lib/markdown2.egg-info/SOURCES.txt 2025-01-24
22:13:43.000000000 +0100
+++ new/markdown2-2.5.4/lib/markdown2.egg-info/SOURCES.txt 2025-07-27
18:16:21.000000000 +0200
@@ -16,6 +16,7 @@
test/api.doctests
test/test.py
test/test_markdown2.py
+test/test_redos.py
test/testall.py
test/testlib.py
test/tm-cases/CVE-2018-5773.html
@@ -141,6 +142,9 @@
test/tm-cases/empty_fenced_code_blocks.opts
test/tm-cases/empty_fenced_code_blocks.tags
test/tm-cases/empty_fenced_code_blocks.text
+test/tm-cases/encode_incomplete_tags_xss_issue625.html
+test/tm-cases/encode_incomplete_tags_xss_issue625.opts
+test/tm-cases/encode_incomplete_tags_xss_issue625.text
test/tm-cases/escape_html_comments_safe_mode.html
test/tm-cases/escape_html_comments_safe_mode.opts
test/tm-cases/escape_html_comments_safe_mode.text
@@ -419,6 +423,12 @@
test/tm-cases/long_link.text
test/tm-cases/malformed_html_crash_issue584.html
test/tm-cases/malformed_html_crash_issue584.text
+test/tm-cases/markdown_file_links.html
+test/tm-cases/markdown_file_links.opts
+test/tm-cases/markdown_file_links.text
+test/tm-cases/markdown_file_links_no_linkdefs.html
+test/tm-cases/markdown_file_links_no_linkdefs.opts
+test/tm-cases/markdown_file_links_no_linkdefs.text
test/tm-cases/markdown_in_html.html
test/tm-cases/markdown_in_html.opts
test/tm-cases/markdown_in_html.tags
@@ -447,6 +457,9 @@
test/tm-cases/middle_word_em.html
test/tm-cases/middle_word_em.opts
test/tm-cases/middle_word_em.text
+test/tm-cases/middle_word_em_issue627.html
+test/tm-cases/middle_word_em_issue627.opts
+test/tm-cases/middle_word_em_issue627.text
test/tm-cases/middle_word_em_with_extra_ems.html
test/tm-cases/middle_word_em_with_extra_ems.opts
test/tm-cases/middle_word_em_with_extra_ems.text
@@ -507,6 +520,9 @@
test/tm-cases/relative_links_safe_mode.html
test/tm-cases/relative_links_safe_mode.opts
test/tm-cases/relative_links_safe_mode.text
+test/tm-cases/safe_mode_issue621.html
+test/tm-cases/safe_mode_issue621.opts
+test/tm-cases/safe_mode_issue621.text
test/tm-cases/script-and-style-blocks.html
test/tm-cases/script-and-style-blocks.text
test/tm-cases/seperated_list_items.html
@@ -522,6 +538,9 @@
test/tm-cases/smarty_pants_issue611.html
test/tm-cases/smarty_pants_issue611.opts
test/tm-cases/smarty_pants_issue611.text
+test/tm-cases/smarty_pants_issue620.html
+test/tm-cases/smarty_pants_issue620.opts
+test/tm-cases/smarty_pants_issue620.text
test/tm-cases/spoiler.html
test/tm-cases/spoiler.opts
test/tm-cases/spoiler.text
@@ -539,6 +558,9 @@
test/tm-cases/tables.opts
test/tm-cases/tables.tags
test/tm-cases/tables.text
+test/tm-cases/tables_nbsp_issue629.html
+test/tm-cases/tables_nbsp_issue629.opts
+test/tm-cases/tables_nbsp_issue629.text
test/tm-cases/task_list.html
test/tm-cases/task_list.opts
test/tm-cases/task_list.tags
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/markdown2-2.5.3/lib/markdown2.py
new/markdown2-2.5.4/lib/markdown2.py
--- old/markdown2-2.5.3/lib/markdown2.py 2024-12-27 19:04:02.000000000
+0100
+++ new/markdown2-2.5.4/lib/markdown2.py 2025-07-27 18:14:04.000000000
+0200
@@ -66,6 +66,7 @@
references, revision number references).
* link-shortrefs: allow shortcut reference links, not followed by `[]` or
a link label.
+* markdown-file-links: Replace links to `.md` files with `.html` links
* markdown-in-html: Allow the use of `markdown="1"` in a block HTML tag to
have markdown processing be done on its contents. Similar to
<http://michelf.com/projects/php-markdown/extra/#markdown-attr> but with
@@ -108,7 +109,7 @@
# not yet sure if there implications with this. Compare 'pydoc sre'
# and 'perldoc perlre'.
-__version_info__ = (2, 5, 3)
+__version_info__ = (2, 5, 4)
__version__ = '.'.join(map(str, __version_info__))
__author__ = "Trent Mick"
@@ -858,9 +859,9 @@
# I broke out the html5 tags here and add them to _block_tags_a and
# _block_tags_b. This way html5 tags are easy to keep track of.
- _html5tags =
'|article|aside|header|hgroup|footer|nav|section|figure|figcaption'
+ _html5tags =
'|address|article|aside|canvas|figcaption|figure|footer|header|main|nav|section|video'
- _block_tags_a =
'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del|style|html|head|body'
+ _block_tags_a =
'blockquote|body|dd|del|div|dl|dt|fieldset|form|h[1-6]|head|hr|html|iframe|ins|li|math|noscript|ol|p|pre|script|style|table|tfoot|ul'
_block_tags_a += _html5tags
_strict_tag_block_re = re.compile(r"""
@@ -876,7 +877,7 @@
""" % _block_tags_a,
re.X | re.M)
- _block_tags_b =
'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math'
+ _block_tags_b =
'blockquote|div|dl|fieldset|form|h[1-6]|iframe|math|noscript|ol|p|pre|script|table|ul'
_block_tags_b += _html5tags
_span_tags = (
@@ -1272,7 +1273,7 @@
\s+ # whitespace after tag
(?:[^\t<>"'=/]+:)?
[^<>"'=/]+= # attr name
- (?:".*?"|'.*?'|[^<>"'=/\s]+) # value, quoted or unquoted.
If unquoted, no spaces allowed
+ (?:"[^"]*?"|'[^']*?'|[^<>"'=/\s]+) # value, quoted or
unquoted. If unquoted, no spaces allowed
)*
\s*/?>
|
@@ -1318,17 +1319,17 @@
is_html_markup = not is_html_markup
return ''.join(escaped)
+ def _is_auto_link(self, text):
+ if ':' in text and self._auto_link_re.match(text):
+ return True
+ elif '@' in text and self._auto_email_link_re.match(text):
+ return True
+ return False
+
@mark_stage(Stage.HASH_HTML)
def _hash_html_spans(self, text: str) -> str:
# Used for safe_mode.
- def _is_auto_link(s):
- if ':' in s and self._auto_link_re.match(s):
- return True
- elif '@' in s and self._auto_email_link_re.match(s):
- return True
- return False
-
def _is_code_span(index, token):
try:
if token == '<code>':
@@ -1352,7 +1353,7 @@
split_tokens = self._sorta_html_tokenize_re.split(text)
is_html_markup = False
for index, token in enumerate(split_tokens):
- if is_html_markup and not _is_auto_link(token) and not
_is_code_span(index, token):
+ if is_html_markup and not self._is_auto_link(token) and not
_is_code_span(index, token):
is_comment = _is_comment(token)
if is_comment:
tokens.append(self._hash_span(self._sanitize_html(is_comment.group(1))))
@@ -1446,25 +1447,6 @@
i += 1
return i
- def _extract_url_and_title(self, text: str, start: int) ->
Union[tuple[str, str, int], tuple[None, None, None]]:
- """Extracts the url and (optional) title from the tail of a link"""
- # text[start] equals the opening parenthesis
- idx = self._find_non_whitespace(text, start+1)
- if idx == len(text):
- return None, None, None
- end_idx = idx
- has_anglebrackets = text[idx] == "<"
- if has_anglebrackets:
- end_idx = self._find_balanced(text, end_idx+1, "<", ">")
- end_idx = self._find_balanced(text, end_idx, "(", ")")
- match = self._inline_link_title.search(text, idx, end_idx)
- if not match:
- return None, None, None
- url, title = text[idx:match.start()], match.group("title")
- if has_anglebrackets:
- url = self._strip_anglebrackets.sub(r'\1', url)
- return url, title, end_idx
-
#
https://developer.mozilla.org/en-US/docs/web/http/basics_of_http/data_urls
#
https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
_data_url_re = re.compile(r'''
@@ -1523,180 +1505,9 @@
Markdown.pl because of the lack of atomic matching support in
Python's regex engine used in $g_nested_brackets.
"""
- MAX_LINK_TEXT_SENTINEL = 3000 # markdown2 issue 24
-
- # `anchor_allowed_pos` is used to support img links inside
- # anchors, but not anchors inside anchors. An anchor's start
- # pos must be `>= anchor_allowed_pos`.
- anchor_allowed_pos = 0
-
- curr_pos = 0
-
- while True:
- # The next '[' is the start of:
- # - an inline anchor: [text](url "title")
- # - a reference anchor: [text][id]
- # - an inline img: 
- # - a reference img: ![text][id]
- # - a footnote ref: [^id]
- # (Only if 'footnotes' extra enabled)
- # - a footnote defn: [^id]: ...
- # (Only if 'footnotes' extra enabled) These have already
- # been stripped in _strip_footnote_definitions() so no
- # need to watch for them.
- # - a link definition: [id]: url "title"
- # These have already been stripped in
- # _strip_link_definitions() so no need to watch for them.
- # - not markup: [...anything else...
- try:
- start_idx = text.index('[', curr_pos)
- except ValueError:
- break
- text_length = len(text)
-
- # Find the matching closing ']'.
- # Markdown.pl allows *matching* brackets in link text so we
- # will here too. Markdown.pl *doesn't* currently allow
- # matching brackets in img alt text -- we'll differ in that
- # regard.
- bracket_depth = 0
-
- for p in range(
- start_idx + 1,
- min(start_idx + MAX_LINK_TEXT_SENTINEL, text_length)
- ):
- ch = text[p]
- if ch == ']':
- bracket_depth -= 1
- if bracket_depth < 0:
- break
- elif ch == '[':
- bracket_depth += 1
- else:
- # Closing bracket not found within sentinel length.
- # This isn't markup.
- curr_pos = start_idx + 1
- continue
- link_text = text[start_idx + 1: p]
-
- # Fix for issue 341 - Injecting XSS into link text
- if self.safe_mode:
- link_text = self._hash_html_spans(link_text)
- link_text = self._unhash_html_spans(link_text)
-
- # Possibly a footnote ref?
- if "footnotes" in self.extras and link_text.startswith("^"):
- normed_id = re.sub(r'\W', '-', link_text[1:])
- if normed_id in self.footnotes:
- result = (
- f'<sup class="footnote-ref" id="fnref-{normed_id}">'
- # insert special footnote marker that's easy to find
and match against later
- f'<a
href="#fn-{normed_id}">{self._footnote_marker}-{normed_id}</a></sup>'
- )
- text = text[:start_idx] + result + text[p+1:]
- else:
- # This id isn't defined, leave the markup alone.
- curr_pos = p + 1
- continue
-
- # Now determine what this is by the remainder.
- p += 1
-
- # -- Extract the URL, title and end index from the link
-
- # inline anchor or inline img
- if text[p:p + 1] == '(':
- url, title, url_end_idx = self._extract_url_and_title(text, p)
- if url is None:
- # text isn't markup
- curr_pos = start_idx + 1
- continue
- url = self._unhash_html_spans(url, code=True)
- # reference anchor or reference img
- else:
- match = None
- if 'link-shortrefs' in self.extras:
- # check if there's no tailing id section
- if link_text and re.match(r'[ ]?(?:\n[ ]*)?(?!\[)',
text[p:]):
- # try a match with `[]` inserted into the text
- match =
self._tail_of_reference_link_re.match(f'{text[:p]}[]{text[p:]}', p)
- if match:
- # if we get a match, we'll have to modify the
`text` variable to insert the `[]`
- # but we ONLY want to do that if the link_id is
valid. This makes sure that we
- # don't get stuck in any loops and also that when
a user inputs `[abc]` we don't
- # output `[abc][]` in the final HTML
- if (match.group("id").lower() or
link_text.lower()) in self.urls:
- text = f'{text[:p]}[]{text[p:]}'
- else:
- match = None
-
- match = match or self._tail_of_reference_link_re.match(text, p)
- if not match:
- # text isn't markup
- curr_pos = start_idx + 1
- continue
-
- link_id = match.group("id").lower() or link_text.lower() #
for links like [this][]
-
- if link_id not in self.urls:
- # This id isn't defined, leave the markup alone.
- # set current pos to end of link title and continue from
there
- curr_pos = p
- continue
-
- url = self.urls[link_id]
- title = self.titles.get(link_id)
- url_end_idx = match.end()
-
- # -- Encode and hash the URL and title to avoid conflicts with
italics/bold
-
- url = (
- url
- .replace('*', self._escape_table['*'])
- .replace('_', self._escape_table['_'])
- )
- if title:
- title = (
- _xml_escape_attr(title)
- .replace('*', self._escape_table['*'])
- .replace('_', self._escape_table['_'])
- )
- title_str = f' title="{title}"'
- else:
- title_str = ''
-
- # -- Process the anchor/image
-
- is_img = start_idx > 0 and text[start_idx-1] == "!"
- if is_img:
- start_idx -= 1
- img_class_str = self._html_class_str_from_tag("img")
- result = result_head = (
- f'<img src="{self._protect_url(url)}"'
- f' alt="{self._hash_span(_xml_escape_attr(link_text))}"'
- f'{title_str}{img_class_str}{self.empty_element_suffix}'
- )
- elif start_idx >= anchor_allowed_pos:
- if self.safe_mode and not self._safe_href.match(url):
- result_head = f'<a href="#"{title_str}>'
- else:
- result_head = f'<a
href="{self._protect_url(url)}"{title_str}>'
- result = f'{result_head}{link_text}</a>'
- else:
- # anchor not allowed here/invalid markup
- curr_pos = start_idx + 1
- continue
-
- if "smarty-pants" in self.extras:
- result = result.replace('"', self._escape_table['"'])
-
- # <img> allowed from curr_pos onwards, <a> allowed from
anchor_allowed_pos onwards.
- # this means images can exist within `<a>` tags but anchors can
only come after the
- # current anchor has been closed
- curr_pos = start_idx + len(result_head)
- anchor_allowed_pos = start_idx + len(result)
- text = text[:start_idx] + result + text[url_end_idx:]
-
+ link_processor = LinkProcessor(self, None)
+ if link_processor.test(text):
+ text = link_processor.run(text)
return text
def header_id_from_text(self,
@@ -2354,7 +2165,7 @@
if self.safe_mode not in ("replace", "escape"):
return text
- if text.endswith(">"):
+ if self._is_auto_link(text):
return text # this is not an incomplete tag, this is a link in
the form <http://x.y.z>
def incomplete_tags_sub(match):
@@ -2649,7 +2460,7 @@
strong_re = Markdown._strong_re
em_re = Markdown._em_re
- def __init__(self, md: Markdown, options: dict):
+ def __init__(self, md: Markdown, options: Optional[dict]):
super().__init__(md, options)
self.hash_table = {}
@@ -2682,6 +2493,342 @@
return '*' in text or '_' in text
return self.hash_table and re.search(r'md5-[0-9a-z]{32}', text)
+
+class _LinkProcessorExtraOpts(TypedDict, total=False):
+ '''Options for the `LinkProcessor` extra'''
+ tags: List[str]
+ '''List of tags to be processed by the extra. Default is `['a', 'img']`'''
+ inline: bool
+ '''Whether to process inline links. Default: True'''
+ ref: bool
+ '''Whether to process reference links. Default: True'''
+
+
+class LinkProcessor(Extra):
+ name = 'link-processor'
+ order = (Stage.ITALIC_AND_BOLD,), (Stage.ESCAPE_SPECIAL,)
+ options: _LinkProcessorExtraOpts
+
+ def __init__(self, md: Markdown, options: Optional[dict]):
+ options = options or {}
+ super().__init__(md, options)
+
+ def parse_inline_anchor_or_image(self, text: str, _link_text: str,
start_idx: int) -> Optional[Tuple[str, str, Optional[str], int]]:
+ '''
+ Parse a string and extract a link from it. This can be an inline
anchor or an image.
+
+ Args:
+ text: the whole text containing the link
+ link_text: the human readable text inside the link
+ start_idx: the index of the link within `text`
+
+ Returns:
+ None if a link was not able to be parsed from `text`.
+ If successful, a tuple is returned containing:
+
+ 1. potentially modified version of the `text` param
+ 2. the URL
+ 3. the title (can be None if not present)
+ 4. the index where the link ends within text
+ '''
+ idx = self.md._find_non_whitespace(text, start_idx + 1)
+ if idx == len(text):
+ return
+ end_idx = idx
+ has_anglebrackets = text[idx] == "<"
+ if has_anglebrackets:
+ end_idx = self.md._find_balanced(text, end_idx+1, "<", ">")
+ end_idx = self.md._find_balanced(text, end_idx, "(", ")")
+ match = self.md._inline_link_title.search(text, idx, end_idx)
+ if not match:
+ return
+ url, title = text[idx:match.start()], match.group("title")
+ if has_anglebrackets:
+ url = self.md._strip_anglebrackets.sub(r'\1', url)
+ return text, url, title, end_idx
+
+ def process_link_shortrefs(self, text: str, link_text: str, start_idx:
int) -> Tuple[Optional[re.Match], str]:
+ '''
+ Detects shortref links within a string and converts them to normal
references
+
+ Args:
+ text: the whole text containing the link
+ link_text: the human readable text inside the link
+ start_idx: the index of the link within `text`
+
+ Returns:
+ A tuple containing:
+
+ 1. A potential `re.Match` against the link reference within `text`
(will be None if not found)
+ 2. potentially modified version of the `text` param
+ '''
+ match = None
+ # check if there's no tailing id section
+ if link_text and re.match(r'[ ]?(?:\n[ ]*)?(?!\[)', text[start_idx:]):
+ # try a match with `[]` inserted into the text
+ match =
self.md._tail_of_reference_link_re.match(f'{text[:start_idx]}[]{text[start_idx:]}',
start_idx)
+ if match:
+ # if we get a match, we'll have to modify the `text` variable
to insert the `[]`
+ # but we ONLY want to do that if the link_id is valid. This
makes sure that we
+ # don't get stuck in any loops and also that when a user
inputs `[abc]` we don't
+ # output `[abc][]` in the final HTML
+ if (match.group("id").lower() or link_text.lower()) in
self.md.urls:
+ text = f'{text[:start_idx]}[]{text[start_idx:]}'
+ else:
+ match = None
+
+ return match, text
+
+ def parse_ref_anchor_or_ref_image(self, text: str, link_text: str,
start_idx: int) -> Optional[Tuple[str, Optional[str], Optional[str], int]]:
+ '''
+ Parse a string and extract a link from it. This can be a reference
anchor or image.
+
+ Args:
+ text: the whole text containing the link
+ link_text: the human readable text inside the link
+ start_idx: the index of the link within `text`
+
+ Returns:
+ None if a link was not able to be parsed from `text`.
+ If successful, a tuple is returned containing:
+
+ 1. potentially modified version of the `text` param
+ 2. the URL (can be None if the reference doesn't exist)
+ 3. the title (can be None if not present)
+ 4. the index where the link ends within text
+ '''
+ match = None
+ if 'link-shortrefs' in self.md.extras:
+ match, text = self.process_link_shortrefs(text, link_text,
start_idx)
+
+ match = match or self.md._tail_of_reference_link_re.match(text,
start_idx)
+ if not match:
+ # text isn't markup
+ return
+
+ link_id = match.group("id").lower() or link_text.lower() # for links
like [this][]
+
+ url = self.md.urls.get(link_id)
+ title = self.md.titles.get(link_id)
+ url_end_idx = match.end()
+
+ return text, url, title, url_end_idx
+
+ def process_image(self, url: str, title_attr: str, link_text: str) ->
Tuple[str, int]:
+ '''
+ Takes a URL, title and link text and returns an HTML `<img>` tag
+
+ Args:
+ url: the image URL/src
+ title_attr: a string containing the title attribute of the tag
(eg: `' title="..."'`)
+ link_text: the human readable text portion of the link
+
+ Returns:
+ A tuple containing:
+
+ 1. The HTML string
+ 2. The length of the opening HTML tag in the string. For `<img>`
it's the whole string.
+ This section will be skipped by the link processor
+ '''
+ img_class_str = self.md._html_class_str_from_tag("img")
+ result = (
+ f'<img src="{self.md._protect_url(url)}"'
+ f' alt="{self.md._hash_span(_xml_escape_attr(link_text))}"'
+ f'{title_attr}{img_class_str}{self.md.empty_element_suffix}'
+ )
+ return result, len(result)
+
+ def process_anchor(self, url: str, title_attr: str, link_text: str) ->
Tuple[str, int]:
+ '''
+ Takes a URL, title and link text and returns an HTML `<a>` tag
+
+ Args:
+ url: the URL
+ title_attr: a string containing the title attribute of the tag
(eg: `' title="..."'`)
+ link_text: the human readable text portion of the link
+
+ Returns:
+ A tuple containing:
+
+ 1. The HTML string
+ 2. The length of the opening HTML tag in the string. This section
will be skipped
+ by the link processor
+ '''
+ if self.md.safe_mode and not self.md._safe_href.match(url):
+ result_head = f'<a href="#"{title_attr}>'
+ else:
+ result_head = f'<a href="{self.md._protect_url(url)}"{title_attr}>'
+
+ return f'{result_head}{link_text}</a>', len(result_head)
+
+ def run(self, text: str):
+ MAX_LINK_TEXT_SENTINEL = 3000 # markdown2 issue 24
+
+ # `anchor_allowed_pos` is used to support img links inside
+ # anchors, but not anchors inside anchors. An anchor's start
+ # pos must be `>= anchor_allowed_pos`.
+ anchor_allowed_pos = 0
+
+ curr_pos = 0
+
+ while True:
+ # The next '[' is the start of:
+ # - an inline anchor: [text](url "title")
+ # - a reference anchor: [text][id]
+ # - an inline img: 
+ # - a reference img: ![text][id]
+ # - a footnote ref: [^id]
+ # (Only if 'footnotes' extra enabled)
+ # - a footnote defn: [^id]: ...
+ # (Only if 'footnotes' extra enabled) These have already
+ # been stripped in _strip_footnote_definitions() so no
+ # need to watch for them.
+ # - a link definition: [id]: url "title"
+ # These have already been stripped in
+ # _strip_link_definitions() so no need to watch for them.
+ # - not markup: [...anything else...
+ try:
+ start_idx = text.index('[', curr_pos)
+ except ValueError:
+ break
+ text_length = len(text)
+
+ # Find the matching closing ']'.
+ # Markdown.pl allows *matching* brackets in link text so we
+ # will here too. Markdown.pl *doesn't* currently allow
+ # matching brackets in img alt text -- we'll differ in that
+ # regard.
+ bracket_depth = 0
+
+ for p in range(
+ start_idx + 1,
+ min(start_idx + MAX_LINK_TEXT_SENTINEL, text_length)
+ ):
+ ch = text[p]
+ if ch == ']':
+ bracket_depth -= 1
+ if bracket_depth < 0:
+ break
+ elif ch == '[':
+ bracket_depth += 1
+ else:
+ # Closing bracket not found within sentinel length.
+ # This isn't markup.
+ curr_pos = start_idx + 1
+ continue
+ link_text = text[start_idx + 1: p]
+
+ # Fix for issue 341 - Injecting XSS into link text
+ if self.md.safe_mode:
+ link_text = self.md._hash_html_spans(link_text)
+ link_text = self.md._unhash_html_spans(link_text)
+
+ # Possibly a footnote ref?
+ if "footnotes" in self.md.extras and link_text.startswith("^"):
+ normed_id = re.sub(r'\W', '-', link_text[1:])
+ if normed_id in self.md.footnotes:
+ result = (
+ f'<sup class="footnote-ref" id="fnref-{normed_id}">'
+ # insert special footnote marker that's easy to find
and match against later
+ f'<a
href="#fn-{normed_id}">{self.md._footnote_marker}-{normed_id}</a></sup>'
+ )
+ text = text[:start_idx] + result + text[p+1:]
+ else:
+ # This id isn't defined, leave the markup alone.
+ curr_pos = p + 1
+ continue
+
+ # Now determine what this is by the remainder.
+ p += 1
+
+ # -- Extract the URL, title and end index from the link
+
+ # inline anchor or inline img
+ if text[p:p + 1] == '(':
+ if not self.options.get('inline', True):
+ curr_pos = start_idx + 1
+ continue
+
+ parsed = self.parse_inline_anchor_or_image(text, link_text, p)
+ if not parsed:
+ # text isn't markup
+ curr_pos = start_idx + 1
+ continue
+
+ text, url, title, url_end_idx = parsed
+ url = self.md._unhash_html_spans(url, code=True)
+ # reference anchor or reference img
+ else:
+ if not self.options.get('ref', True):
+ curr_pos = start_idx + 1
+ continue
+
+ parsed = self.parse_ref_anchor_or_ref_image(text, link_text, p)
+ if not parsed:
+ curr_pos = start_idx + 1
+ continue
+
+ text, url, title, url_end_idx = parsed
+ if url is None:
+ # This id isn't defined, leave the markup alone.
+ # set current pos to end of link title and continue from
there
+ curr_pos = p
+ continue
+
+ # -- Encode and hash the URL and title to avoid conflicts with
italics/bold
+
+ url = (
+ url
+ .replace('*', self.md._escape_table['*'])
+ .replace('_', self.md._escape_table['_'])
+ )
+ if title:
+ title = (
+ _xml_escape_attr(title)
+ .replace('*', self.md._escape_table['*'])
+ .replace('_', self.md._escape_table['_'])
+ )
+ title_str = f' title="{title}"'
+ else:
+ title_str = ''
+
+ # -- Process the anchor/image
+
+ is_img = start_idx > 0 and text[start_idx-1] == "!"
+ if is_img:
+ if 'img' not in self.options.get('tags', ['img']):
+ curr_pos = start_idx + 1
+ continue
+
+ start_idx -= 1
+ result, skip = self.process_image(url, title_str, link_text)
+ elif start_idx >= anchor_allowed_pos:
+ if 'a' not in self.options.get('tags', ['a']):
+ curr_pos = start_idx + 1
+ continue
+
+ result, skip = self.process_anchor(url, title_str, link_text)
+ else:
+ # anchor not allowed here/invalid markup
+ curr_pos = start_idx + 1
+ continue
+
+ if "smarty-pants" in self.md.extras:
+ result = result.replace('"', self.md._escape_table['"'])
+
+ # <img> allowed from curr_pos onwards, <a> allowed from
anchor_allowed_pos onwards.
+ # this means images can exist within `<a>` tags but anchors can
only come after the
+ # current anchor has been closed
+ curr_pos = start_idx + skip
+ anchor_allowed_pos = start_idx + len(result)
+ text = text[:start_idx] + result + text[url_end_idx:]
+
+ return text
+
+ def test(self, text):
+ return '(' in text or '[' in text
+
+
# User facing extras
# ----------------------------------------------------------
@@ -3085,6 +3232,48 @@
return True
+class _MarkdownFileLinksExtraOpts(_LinkProcessorExtraOpts, total=False):
+ '''Options for the `MarkdownFileLinks` extra'''
+ link_defs: bool
+ '''Whether to convert link definitions as well. Default: True'''
+
+
+class MarkdownFileLinks(LinkProcessor):
+ '''
+ Replace links to `.md` files with `.html` links
+ '''
+
+ name = 'markdown-file-links'
+ order = (Stage.LINKS,), (Stage.LINK_DEFS,)
+ options: _MarkdownFileLinksExtraOpts
+
+ def __init__(self, md: Markdown, options: Optional[dict]):
+ # override LinkProcessor defaults
+ options = {'tags': ['a'], 'ref': False, **(options or {})}
+ super().__init__(md, options)
+
+ def parse_inline_anchor_or_image(self, text: str, _link_text: str,
start_idx: int):
+ result = super().parse_inline_anchor_or_image(text, _link_text,
start_idx)
+ if not result or not result[1] or not result[1].endswith('.md'):
+ # return None for invalid markup, or links that don't end with
'.md'
+ # so that we don't touch them, and other extras can process them
freely
+ return
+ url = result[1].removesuffix('.md') + '.html'
+ return result[0], url, *result[2:]
+
+ def run(self, text: str):
+ if Stage.LINKS > self.md.order > Stage.LINK_DEFS and
self.options.get('link_defs', True):
+ # running just after link defs have been stripped
+ for key, url in self.md.urls.items():
+ if url.endswith('.md'):
+ self.md.urls[key] = url.removesuffix('.md') + '.html'
+
+ return super().run(text)
+
+ def test(self, text):
+ return super().test(text) and '.md' in text
+
+
class Mermaid(FencedCodeBlocks):
name = 'mermaid'
order = (FencedCodeBlocks,), ()
@@ -3104,7 +3293,7 @@
name = 'middle-word-em'
order = (CodeFriendly,), (Stage.ITALIC_AND_BOLD,)
- def __init__(self, md: Markdown, options: Union[dict, bool]):
+ def __init__(self, md: Markdown, options: Union[dict, bool, None]):
'''
Args:
md: the markdown instance
@@ -3115,6 +3304,8 @@
'''
if isinstance(options, bool):
options = {'allowed': options}
+ else:
+ options = options or {}
options.setdefault('allowed', True)
super().__init__(md, options)
@@ -3385,7 +3576,7 @@
return table_re.sub(self.sub, text)
def sub(self, match: re.Match) -> str:
- trim_space_re = '^[ \t\n]+|[ \t\n]+$'
+ trim_space_re = r'^\s+|\s+$'
trim_bar_re = r'^\||\|$'
split_bar_re = r'^\||(?<![\`\\])\|'
escape_bar_re = r'\\\|'
@@ -3583,6 +3774,7 @@
Latex.register()
LinkPatterns.register()
MarkdownInHTML.register()
+MarkdownFileLinks.register()
MiddleWordEm.register()
Mermaid.register()
Numbering.register()
@@ -3909,8 +4101,6 @@
.replace('<', '<')
.replace('>', '>'))
if safe_mode:
- if charset != 'base64':
- escaped = escaped.replace('+', ' ')
escaped = escaped.replace("'", "'")
return escaped
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/markdown2-2.5.3/test/test_redos.py
new/markdown2-2.5.4/test/test_redos.py
--- old/markdown2-2.5.3/test/test_redos.py 1970-01-01 01:00:00.000000000
+0100
+++ new/markdown2-2.5.4/test/test_redos.py 2025-07-27 18:14:04.000000000
+0200
@@ -0,0 +1,90 @@
+import logging
+import subprocess
+import sys
+import time
+from pathlib import Path
+
+log = logging.getLogger("test")
+LIB_DIR = Path(__file__).parent.parent / "lib"
+
+
+def pull_387_example_1():
+ # https://github.com/trentm/python-markdown2/pull/387
+ return "[#a" + " " * 3456
+
+
+def pull_387_example_2():
+ # https://github.com/trentm/python-markdown2/pull/387
+ return "```" + "\n" * 3456
+
+
+def pull_387_example_3():
+ # https://github.com/trentm/python-markdown2/pull/387
+ return "-*-" + " " * 3456
+
+
+def pull_402():
+ # https://github.com/trentm/python-markdown2/pull/402
+ return " " * 100_000 + "$"
+
+
+def issue493():
+ # https://github.com/trentm/python-markdown2/issues/493
+ return "**_" + "*_" * 38730 * 10 + "\x00"
+
+
+def issue_633():
+ # https://github.com/trentm/python-markdown2/issues/633
+ return '<p m="1"' * 2500 + " " * 5000 + "</div"
+
+
+# whack everything in a dict for easy lookup later on
+CASES = {
+ fn.__name__: fn
+ for fn in [
+ pull_387_example_1,
+ pull_387_example_2,
+ pull_387_example_3,
+ pull_402,
+ issue493,
+ issue_633,
+ ]
+}
+
+
+if __name__ == "__main__":
+ logging.basicConfig()
+
+ if "--execute" in sys.argv:
+ testcase = CASES[sys.argv[sys.argv.index("--execute") + 1]]
+ sys.path.insert(0, str(LIB_DIR))
+ from markdown2 import markdown
+
+ markdown(testcase())
+ sys.exit(0)
+
+ print("-- ReDoS tests")
+
+ fails = []
+ start_time = time.time()
+ for testcase in CASES:
+ print(f"markdown2/redos/{testcase} ... ", end="")
+
+ testcase_start_time = time.time()
+ try:
+ subprocess.run([sys.executable, __file__, "--execute", testcase],
timeout=3)
+ except subprocess.TimeoutExpired:
+ fails.append(testcase)
+ print(f"FAIL ({time.time() - testcase_start_time:.3f}s)")
+ else:
+ print(f"ok ({time.time() - testcase_start_time:.3f}s)")
+
+
print("----------------------------------------------------------------------")
+ print(f"Ran {len(CASES)} tests in {time.time() - start_time:.3f}s")
+
+ if fails:
+ print("FAIL:", fails)
+ else:
+ print("OK")
+
+ sys.exit(len(fails))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/markdown2-2.5.3/test/testall.py
new/markdown2-2.5.4/test/testall.py
--- old/markdown2-2.5.3/test/testall.py 2024-12-11 22:59:09.000000000 +0100
+++ new/markdown2-2.5.4/test/testall.py 2025-05-18 21:25:25.000000000 +0200
@@ -17,7 +17,7 @@
assert ' ' not in python
o = os.popen('''%s -c "import sys; print(sys.version)"''' % python)
ver_str = o.read().strip()
- ver_bits = re.split(r"\.|[^\d]", ver_str, 2)[:2]
+ ver_bits = re.split(r"\.|[^\d]", ver_str, maxsplit=2)[:2]
ver = tuple(map(int, ver_bits))
return ver
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/encode_incomplete_tags_xss_issue625.html
new/markdown2-2.5.4/test/tm-cases/encode_incomplete_tags_xss_issue625.html
--- old/markdown2-2.5.3/test/tm-cases/encode_incomplete_tags_xss_issue625.html
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/encode_incomplete_tags_xss_issue625.html
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+<p><x><img src=x onerror=alert("xss")//><x></p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/encode_incomplete_tags_xss_issue625.opts
new/markdown2-2.5.4/test/tm-cases/encode_incomplete_tags_xss_issue625.opts
--- old/markdown2-2.5.3/test/tm-cases/encode_incomplete_tags_xss_issue625.opts
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/encode_incomplete_tags_xss_issue625.opts
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+{'safe_mode': 'escape'}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/encode_incomplete_tags_xss_issue625.text
new/markdown2-2.5.4/test/tm-cases/encode_incomplete_tags_xss_issue625.text
--- old/markdown2-2.5.3/test/tm-cases/encode_incomplete_tags_xss_issue625.text
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/encode_incomplete_tags_xss_issue625.text
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+<x><img src=x onerror=alert("xss")//><x>
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/fenced_code_blocks_issue426.html
new/markdown2-2.5.4/test/tm-cases/fenced_code_blocks_issue426.html
--- old/markdown2-2.5.3/test/tm-cases/fenced_code_blocks_issue426.html
2023-06-22 23:05:35.000000000 +0200
+++ new/markdown2-2.5.4/test/tm-cases/fenced_code_blocks_issue426.html
2025-05-18 21:25:25.000000000 +0200
@@ -15,7 +15,7 @@
<li><p><code>ContextMixin</code> defines the method
<code>get_context_data</code>:</p>
<div class="codehilite">
-<pre><span></span><code><span class="k">def</span> <span
class="nf">get_context_data</span><span class="p">(</span><span
class="bp">self</span><span class="p">,</span> <span class="o">**</span><span
class="n">kwargs</span><span class="p">):</span>
+<pre><span></span><code><span class="k">def</span><span class="w">
</span><span class="nf">get_context_data</span><span class="p">(</span><span
class="bp">self</span><span class="p">,</span> <span class="o">**</span><span
class="n">kwargs</span><span class="p">):</span>
<span class="n">kwargs</span><span class="o">.</span><span
class="n">setdefault</span><span class="p">(</span><span
class="s1">'view'</span><span class="p">,</span> <span
class="bp">self</span><span class="p">)</span>
<span class="k">if</span> <span class="bp">self</span><span
class="o">.</span><span class="n">extra_context</span> <span
class="ow">is</span> <span class="ow">not</span> <span
class="kc">None</span><span class="p">:</span>
<span class="n">kwargs</span><span class="o">.</span><span
class="n">update</span><span class="p">(</span><span
class="bp">self</span><span class="o">.</span><span
class="n">extra_context</span><span class="p">)</span>
@@ -26,7 +26,7 @@
<p>So when overriding one must be careful to extends <code>super</code>'s
<code>kwargs</code>:</p>
<div class="codehilite">
-<pre><span></span><code><span class="k">def</span> <span
class="nf">get_context_data</span><span class="p">(</span><span
class="bp">self</span><span class="p">,</span> <span class="o">**</span><span
class="n">kwargs</span><span class="p">):</span>
+<pre><span></span><code><span class="k">def</span><span class="w">
</span><span class="nf">get_context_data</span><span class="p">(</span><span
class="bp">self</span><span class="p">,</span> <span class="o">**</span><span
class="n">kwargs</span><span class="p">):</span>
<span class="n">kwargs</span> <span class="o">=</span> <span
class="nb">super</span><span class="p">()</span><span class="o">.</span><span
class="n">get_context_data</span><span class="p">(</span><span
class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="n">kwargs</span><span class="p">[</span><span
class="s1">'page_title'</span><span class="p">]</span> <span
class="o">=</span> <span class="s2">"Documentation"</span>
<span class="k">return</span> <span class="n">kwargs</span>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/fenced_code_blocks_syntax_indentation.html
new/markdown2-2.5.4/test/tm-cases/fenced_code_blocks_syntax_indentation.html
---
old/markdown2-2.5.3/test/tm-cases/fenced_code_blocks_syntax_indentation.html
2023-06-22 23:05:35.000000000 +0200
+++
new/markdown2-2.5.4/test/tm-cases/fenced_code_blocks_syntax_indentation.html
2025-05-18 21:25:25.000000000 +0200
@@ -1,5 +1,5 @@
<div class="codehilite">
-<pre><span></span><code><span class="k">def</span> <span
class="nf">foo</span><span class="p">():</span>
+<pre><span></span><code><span class="k">def</span><span class="w">
</span><span class="nf">foo</span><span class="p">():</span>
<span class="nb">print</span> <span class="s2">"foo"</span>
<span class="nb">print</span> <span class="s2">"bar"</span>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/markdown_file_links.html
new/markdown2-2.5.4/test/tm-cases/markdown_file_links.html
--- old/markdown2-2.5.3/test/tm-cases/markdown_file_links.html 1970-01-01
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/markdown_file_links.html 2025-05-18
21:25:25.000000000 +0200
@@ -0,0 +1,3 @@
+<p><a href="./file.html">This is a link to a markdown file</a></p>
+
+<p><a href="./something.html">This is a reference to a markdown file
link</a></p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/markdown_file_links.opts
new/markdown2-2.5.4/test/tm-cases/markdown_file_links.opts
--- old/markdown2-2.5.3/test/tm-cases/markdown_file_links.opts 1970-01-01
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/markdown_file_links.opts 2025-05-18
21:25:25.000000000 +0200
@@ -0,0 +1 @@
+{'extras': ['markdown-file-links']}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/markdown_file_links.text
new/markdown2-2.5.4/test/tm-cases/markdown_file_links.text
--- old/markdown2-2.5.3/test/tm-cases/markdown_file_links.text 1970-01-01
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/markdown_file_links.text 2025-05-18
21:25:25.000000000 +0200
@@ -0,0 +1,6 @@
+[This is a link to a markdown file](./file.md)
+
+[This is a reference to a markdown file link][]
+
+
+[This is a reference to a markdown file link]: ./something.md
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/markdown_file_links_no_linkdefs.html
new/markdown2-2.5.4/test/tm-cases/markdown_file_links_no_linkdefs.html
--- old/markdown2-2.5.3/test/tm-cases/markdown_file_links_no_linkdefs.html
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/markdown_file_links_no_linkdefs.html
2025-05-18 21:25:25.000000000 +0200
@@ -0,0 +1 @@
+<p><a href="./something.md">This is a reference to a markdown file link</a>
but link definition swapping is disabled</p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/markdown_file_links_no_linkdefs.opts
new/markdown2-2.5.4/test/tm-cases/markdown_file_links_no_linkdefs.opts
--- old/markdown2-2.5.3/test/tm-cases/markdown_file_links_no_linkdefs.opts
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/markdown_file_links_no_linkdefs.opts
2025-05-18 21:25:25.000000000 +0200
@@ -0,0 +1 @@
+{'extras': {'markdown-file-links': {'link_defs': False}}}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/markdown_file_links_no_linkdefs.text
new/markdown2-2.5.4/test/tm-cases/markdown_file_links_no_linkdefs.text
--- old/markdown2-2.5.3/test/tm-cases/markdown_file_links_no_linkdefs.text
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/markdown_file_links_no_linkdefs.text
2025-05-18 21:25:25.000000000 +0200
@@ -0,0 +1,4 @@
+[This is a reference to a markdown file link][] but link definition swapping
is disabled
+
+
+[This is a reference to a markdown file link]: ./something.md
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/middle_word_em_issue627.html
new/markdown2-2.5.4/test/tm-cases/middle_word_em_issue627.html
--- old/markdown2-2.5.3/test/tm-cases/middle_word_em_issue627.html
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/middle_word_em_issue627.html
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+<p>abc<em>def</em>ghi</p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/middle_word_em_issue627.opts
new/markdown2-2.5.4/test/tm-cases/middle_word_em_issue627.opts
--- old/markdown2-2.5.3/test/tm-cases/middle_word_em_issue627.opts
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/middle_word_em_issue627.opts
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+{'extras': {'middle-word-em': None}}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/middle_word_em_issue627.text
new/markdown2-2.5.4/test/tm-cases/middle_word_em_issue627.text
--- old/markdown2-2.5.3/test/tm-cases/middle_word_em_issue627.text
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/middle_word_em_issue627.text
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+abc_def_ghi
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/safe_mode_issue621.html
new/markdown2-2.5.4/test/tm-cases/safe_mode_issue621.html
--- old/markdown2-2.5.3/test/tm-cases/safe_mode_issue621.html 1970-01-01
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/safe_mode_issue621.html 2025-05-18
22:02:26.000000000 +0200
@@ -0,0 +1 @@
+<p><a
href="https://chromium.googlesource.com/v8/v8.git/+/refs/heads/beta">Chromium</a></p>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/safe_mode_issue621.opts
new/markdown2-2.5.4/test/tm-cases/safe_mode_issue621.opts
--- old/markdown2-2.5.3/test/tm-cases/safe_mode_issue621.opts 1970-01-01
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/safe_mode_issue621.opts 2025-05-18
22:02:26.000000000 +0200
@@ -0,0 +1 @@
+{'safe_mode': 'escape'}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/safe_mode_issue621.text
new/markdown2-2.5.4/test/tm-cases/safe_mode_issue621.text
--- old/markdown2-2.5.3/test/tm-cases/safe_mode_issue621.text 1970-01-01
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/safe_mode_issue621.text 2025-05-18
22:02:26.000000000 +0200
@@ -0,0 +1 @@
+[Chromium](https://chromium.googlesource.com/v8/v8.git/+/refs/heads/beta)
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/smarty_pants_issue620.html
new/markdown2-2.5.4/test/tm-cases/smarty_pants_issue620.html
--- old/markdown2-2.5.3/test/tm-cases/smarty_pants_issue620.html
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/smarty_pants_issue620.html
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1,15 @@
+<p>Some HTML5 block level elements that should be hashed so that smarty-pants
doesn’t interfere</p>
+
+<address>
+ <a href="mailto:[email protected]">[email protected]</a><br />
+ <a href="tel:+14155550132">+1 (415) 555‑0132</a>
+</address>
+
+<canvas width="120" height="120">
+ An alternative text describing what your canvas displays.
+</canvas>
+
+<video height="640" width="382" controls loop>
+ <source src="images/door.mp4" type="video/mp4"/>
+ Test
+</video>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/smarty_pants_issue620.opts
new/markdown2-2.5.4/test/tm-cases/smarty_pants_issue620.opts
--- old/markdown2-2.5.3/test/tm-cases/smarty_pants_issue620.opts
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/smarty_pants_issue620.opts
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1 @@
+{"extras": ["smarty-pants"]}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/smarty_pants_issue620.text
new/markdown2-2.5.4/test/tm-cases/smarty_pants_issue620.text
--- old/markdown2-2.5.3/test/tm-cases/smarty_pants_issue620.text
1970-01-01 01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/smarty_pants_issue620.text
2025-05-18 22:02:26.000000000 +0200
@@ -0,0 +1,15 @@
+Some HTML5 block level elements that should be hashed so that smarty-pants
doesn't interfere
+
+<address>
+ <a href="mailto:[email protected]">[email protected]</a><br />
+ <a href="tel:+14155550132">+1 (415) 555‑0132</a>
+</address>
+
+<canvas width="120" height="120">
+ An alternative text describing what your canvas displays.
+</canvas>
+
+<video height="640" width="382" controls loop>
+ <source src="images/door.mp4" type="video/mp4"/>
+ Test
+</video>
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/tables_nbsp_issue629.html
new/markdown2-2.5.4/test/tm-cases/tables_nbsp_issue629.html
--- old/markdown2-2.5.3/test/tm-cases/tables_nbsp_issue629.html 1970-01-01
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/tables_nbsp_issue629.html 2025-07-27
18:14:04.000000000 +0200
@@ -0,0 +1,14 @@
+<table>
+<thead>
+<tr>
+ <th>A</th>
+ <th>B</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>Foo</td>
+ <td>123</td>
+</tr>
+</tbody>
+</table>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/tables_nbsp_issue629.opts
new/markdown2-2.5.4/test/tm-cases/tables_nbsp_issue629.opts
--- old/markdown2-2.5.3/test/tm-cases/tables_nbsp_issue629.opts 1970-01-01
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/tables_nbsp_issue629.opts 2025-07-27
18:14:04.000000000 +0200
@@ -0,0 +1 @@
+{'extras': ['tables']}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/markdown2-2.5.3/test/tm-cases/tables_nbsp_issue629.text
new/markdown2-2.5.4/test/tm-cases/tables_nbsp_issue629.text
--- old/markdown2-2.5.3/test/tm-cases/tables_nbsp_issue629.text 1970-01-01
01:00:00.000000000 +0100
+++ new/markdown2-2.5.4/test/tm-cases/tables_nbsp_issue629.text 2025-07-27
18:14:04.000000000 +0200
@@ -0,0 +1,3 @@
+| A | B |
+|-----|-----|
+| Foo | 123 |
\ No newline at end of file