Skip to content

Commit c6652fe

Browse files
committed
Support markdown2 2.4.10, 2.4.8- and exclude 2.4.9
Handle these changes to markdown2 version 2.4.9 broke links like (issue1)[issue1]: raise error if used Version 2.4.10 changed how filtering of schemes is done: adapt to new method Mail url's in markdown are formatted [label](mailto:[email protected]). The markdown format wrapper uses the plain text formatter to turn issue1 and [email protected] into markdown formatted strings to be htmlized by the markdown formatters. However when the plain text formatter saw (mailto:[email protected]) it made it (mailto:<[email protected]>). This is broken as the enamil address shouldn't have the angle brackets. By modifying the email pattern to include an optional mailto:, all three markdown formatters do the right thing and I don't end up with href="<[email protected]>" in the link.
1 parent 4e16054 commit c6652fe

File tree

3 files changed

+132
-17
lines changed

3 files changed

+132
-17
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ Fixed:
2222
- issue2551184 - improve i18n handling. Patch to test to make sure it
2323
uses the test tracker's locale files and not other locale
2424
files. (Marcus Priesch)
25+
- issue2551283 - fail if version 2.4.9 of markdown2 is used, it's
26+
broken. Support version 2.4.10 with its new schema filtering
27+
method and 2.4.8 and earlier. (John Rouillard)
2528

2629
Features:
2730

roundup/cgi/templating.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,49 @@ def _import_markdown2():
6161
import markdown2
6262
import re
6363

64-
class Markdown(markdown2.Markdown):
65-
# don't allow disabled protocols in links
66-
_safe_protocols = re.compile('(?!' + ':|'.join([
67-
re.escape(s) for s in _disable_url_schemes])
64+
# Note: version 2.4.9 does not work with Roundup as it breaks
65+
# [issue1](issue1) formatted links.
66+
67+
# Versions 2.4.8 and 2.4.10 use different methods to filter
68+
# allowed schemes. 2.4.8 uses a pre-compiled regexp while
69+
# 2.4.10 uses a regexp string that it compiles.
70+
71+
markdown2_vi = markdown2.__version_info__
72+
if markdown2_vi > (2, 4, 9):
73+
# Create the filtering regexp.
74+
# Allowed default is same as what hyper_re supports.
75+
76+
# pathed_schemes are terminated with ://
77+
pathed_schemes = [ 'http', 'https', 'ftp', 'ftps' ]
78+
# non_pathed are terminated with a :
79+
non_pathed_schemes = [ "mailto" ]
80+
81+
for disabled in _disable_url_schemes:
82+
try:
83+
pathed_schemes.remove(disabled)
84+
except ValueError: # if disabled not in list
85+
pass
86+
try:
87+
non_pathed_schemes.remove(disabled)
88+
except ValueError:
89+
pass
90+
91+
re_list = []
92+
for scheme in pathed_schemes:
93+
re_list.append(r'(?:%s)://' % scheme)
94+
for scheme in non_pathed_schemes:
95+
re_list.append(r'(?:%s):' % scheme)
96+
97+
enabled_schemes = r"|".join(re_list)
98+
class Markdown(markdown2.Markdown):
99+
_safe_protocols = enabled_schemes
100+
elif markdown2_vi == (2, 4, 9):
101+
raise RuntimeError("Unsupported version - markdown2 v2.4.9\n")
102+
else:
103+
class Markdown(markdown2.Markdown):
104+
# don't allow disabled protocols in links
105+
_safe_protocols = re.compile('(?!' + ':|'.join([
106+
re.escape(s) for s in _disable_url_schemes])
68107
+ ':)', re.IGNORECASE)
69108

70109
def _extras(config):
@@ -1639,7 +1678,7 @@ class StringHTMLProperty(HTMLProperty):
16391678
(:[\d]{1,5})? # port
16401679
(/[\w\-$.+!*(),;:@&=?/~\\#%]*)? # path etc.
16411680
)|
1642-
(?P<email>[-+=%/\w\.]+@[\w\.\-]+)|
1681+
(?P<email>(?:mailto:)?[-+=%/\w\.]+@[\w\.\-]+)|
16431682
(?P<item>(?P<class>[A-Za-z_]+)(\s*)(?P<id>\d+)(?P<fragment>\#[^][\#%^{}"<>\s]+)?)
16441683
)''', re.X | re.I)
16451684
protocol_re = re.compile('^(ht|f)tp(s?)://', re.I)

test/test_templating.py

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import pytest
1212
from .pytest_patcher import mark_class
1313

14+
from markdown2 import __version_info__ as md2__version_info__
15+
1416
if ReStructuredText:
1517
skip_rst = lambda func, *args, **kwargs: func
1618
else:
@@ -774,51 +776,122 @@ def test_string_markdown(self):
774776
self.assertEqual(p.markdown().strip(), u2s(u'<p>A string with &lt;br&gt; <em>embedded</em> \u00df</p>'))
775777

776778
def test_string_markdown_link(self):
777-
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'A link <http://localhost>'))
778-
self.assertEqual(p.markdown().strip(), u2s(u'<p>A link <a href="http://localhost">http://localhost</a></p>'))
779+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test',
780+
u2s(u'A link <http://localhost>'))
781+
m = p.markdown().strip()
782+
m = self.mangleMarkdown2(m)
783+
784+
self.assertEqual( u2s(u'<p>A link <a href="http://localhost" rel="nofollow noopener">http://localhost</a></p>'), m)
779785

780786
def test_string_markdown_link_item(self):
781787
""" The link formats for the different markdown engines changes.
782788
Order of attributes, value for rel (noopener, nofollow etc)
783789
is different. So most tests check for a substring that indicates
784790
success rather than the entire returned string.
785791
"""
786-
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'An issue1 link'))
792+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test',
793+
u2s(u'An issue1 link'))
787794
self.assertIn( u2s(u'href="issue1"'), p.markdown().strip())
788795
# just verify that plain linking is working
789796
self.assertIn( u2s(u'href="issue1"'), p.plain(hyperlink=1))
790797

791-
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'An [issue1](issue1) link'))
798+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test',
799+
u2s(u'An [issue1](issue1) link'))
792800
self.assertIn( u2s(u'href="issue1"'), p.markdown().strip())
793801
# just verify that plain linking is working
794802
self.assertIn( u2s(u'href="issue1"'), p.plain(hyperlink=1))
795803

796-
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'An [issue1](https://example.com/issue1) link'))
797-
self.assertIn( u2s(u'href="https://example.com/issue1"'), p.markdown().strip())
804+
p = StringHTMLProperty(
805+
self.client, 'test', '1', None, 'test',
806+
u2s(u'An [issue1](https://example.com/issue1) link'))
807+
self.assertIn( u2s(u'href="https://example.com/issue1"'),
808+
p.markdown().strip())
809+
810+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test',
811+
u2s(u'An [issu1](#example) link'))
812+
self.assertIn( u2s(u'href="#example"'), p.markdown().strip())
813+
814+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test',
815+
u2s(u'An [issu1](/example) link'))
816+
self.assertIn( u2s(u'href="/example"'), p.markdown().strip())
817+
818+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test',
819+
u2s(u'An [issu1](./example) link'))
820+
self.assertIn( u2s(u'href="./example"'), p.markdown().strip())
798821

799-
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'An [issue1] (https://example.com/issue1) link'))
822+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test',
823+
u2s(u'An [issu1](../example) link'))
824+
self.assertIn( u2s(u'href="../example"'), p.markdown().strip())
825+
826+
p = StringHTMLProperty(
827+
self.client, 'test', '1', None, 'test',
828+
u2s(u'A [wuarchive_ftp](ftp://www.wustl.gov/file) link'))
829+
self.assertIn( u2s(u'href="ftp://www.wustl.gov/file"'),
830+
p.markdown().strip())
831+
832+
p = StringHTMLProperty(
833+
self.client, 'test', '1', None, 'test',
834+
u2s(u'An [issue1] (https://example.com/issue1) link'))
800835
self.assertIn( u2s(u'href="issue1"'), p.markdown().strip())
801836
if type(self) == MistuneTestCase:
802837
# mistune makes the https url into a real link
803-
self.assertIn( u2s(u'href="https://example.com/issue1"'), p.markdown().strip())
838+
self.assertIn( u2s(u'href="https://example.com/issue1"'),
839+
p.markdown().strip())
804840
else:
805841
# the other two engines leave the parenthesized url as is.
806-
self.assertIn( u2s(u' (https://example.com/issue1) link'), p.markdown().strip())
842+
self.assertIn( u2s(u' (https://example.com/issue1) link'),
843+
p.markdown().strip())
807844

808-
def test_string_markdown_link(self):
845+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test',
846+
u2s(u'An [issu1](.../example) link'))
847+
if (isinstance(self, Markdown2TestCase) and
848+
md2__version_info__ > (2, 4, 9)):
849+
# markdown2 > 2.4.9 handles this differently
850+
self.assertIn( u2s(u'href="#"'), p.markdown().strip())
851+
else:
852+
self.assertIn( u2s(u'href=".../example"'), p.markdown().strip())
853+
854+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test',
855+
u2s(u'A [phone](tel:0016175555555) link'))
856+
if (isinstance(self, Markdown2TestCase) and
857+
md2__version_info__ > (2, 4, 9)):
858+
self.assertIn(u2s(u'href="#"'), p.markdown().strip())
859+
else:
860+
self.assertIn( u2s(u'href="tel:0016175555555"'),
861+
p.markdown().strip())
862+
863+
def test_string_email_markdown_link(self):
809864
# markdown2 and markdown escape the email address
810865
try:
811866
from html import unescape as html_unescape
812867
except ImportError:
813868
from HTMLParser import HTMLParser
814869
html_unescape = HTMLParser().unescape
815870

816-
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'A link <[email protected]>'))
871+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test',
872+
u2s(u'A link <[email protected]>'))
817873
m = html_unescape(p.markdown().strip())
818874
m = self.mangleMarkdown2(m)
819875

820876
self.assertEqual(m, u2s(u'<p>A link <a href="mailto:[email protected]">[email protected]</a></p>'))
821877

878+
p = StringHTMLProperty(
879+
self.client, 'test', '1', None, 'test',
880+
u2s(u'An bare email [email protected] link'))
881+
m = self.mangleMarkdown2(html_unescape(p.markdown().strip()))
882+
self.assertIn( u2s(u'href="mailto:[email protected]"'),
883+
m)
884+
885+
p = StringHTMLProperty(
886+
self.client, 'test', '1', None, 'test',
887+
u2s(u'An [email_url](mailto:[email protected]) link'))
888+
m = self.mangleMarkdown2(html_unescape(p.markdown().strip()))
889+
890+
if isinstance(self, MistuneTestCase):
891+
self.assertIn('<a href="mailto:[email protected]" rel="nofollow noopener">email_url</a>', m)
892+
else:
893+
self.assertIn('<a href="mailto:[email protected]">email_url</a>', m)
894+
822895
def test_string_markdown_javascript_link(self):
823896
# make sure we don't get a "javascript:" link
824897
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'<javascript:alert(1)>'))
@@ -866,7 +939,7 @@ def test_string_markdown_code_block_attribute(self):
866939
if type(self) == MistuneTestCase:
867940
self.assertEqual(m, parser.normalize('<p>embedded code block &lt;pre&gt;</p>\n<pre><code class="lang-python">line 1\nline 2\n</code></pre>\n<p>new &lt;/pre&gt; paragraph</p>'))
868941
elif type(self) == MarkdownTestCase:
869-
self.assertEqual(m, parser.normalize('<p>embedded code block &lt;pre&gt;</p>\n<pre><code class="language-python">line 1\nline 2\n</code></pre>\n<p>new &lt;/pre&gt; paragraph</p>'))
942+
self.assertEqual(m.replace('class="python"','class="language-python"'), parser.normalize('<p>embedded code block &lt;pre&gt;</p>\n<pre><code class="language-python">line 1\nline 2\n</code></pre>\n<p>new &lt;/pre&gt; paragraph</p>'))
870943
else:
871944
expected_result = parser.normalize('<p>embedded code block &lt;pre&gt;</p>\n<div class="codehilite"><pre><span></span><code><span class="n">line</span> <span class="mi">1</span>\n<span class="n">line</span> <span class="mi">2</span>\n</code></pre></div>\n<p>new &lt;/pre&gt; paragraph</p>')
872945
self.assertEqual(m, expected_result)

0 commit comments

Comments
 (0)