Skip to content

Commit 11ad026

Browse files
committed
escape HTML tags in markdown content
enabled fenced code blocks for markdown allow mistune to be used as a markdown parser test all installed markdown parsers and fallback code
1 parent 79250e1 commit 11ad026

File tree

4 files changed

+172
-60
lines changed

4 files changed

+172
-60
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ install:
9494
- pip install gpg pytz whoosh pyjwt
9595
- pip install pytest-cov codecov
9696
- if [[ $TRAVIS_PYTHON_VERSION != "3.4"* ]]; then pip install docutils; fi
97+
- if [[ $TRAVIS_PYTHON_VERSION != "3.4"* ]]; then pip install mistune; fi
98+
- if [[ $TRAVIS_PYTHON_VERSION == "3.[5-9]"* ]]; then pip install Markdown; fi
9799
- pip install markdown2
98100

99101
before_script:

doc/installation.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ docutils
120120
To use ReStructuredText rendering you need to have the docutils
121121
package installed.
122122

123-
markdown or markdown2
124-
To use markdown rendering you need to either have the markdown or
125-
markdown2 package installed.
123+
markdown, markdown2 or mistune
124+
To use markdown rendering you need to have the markdown, markdown2
125+
or mistune package installed.
126126

127127
Windows Service
128128
You can run Roundup as a Windows service if pywin32_ is installed.
@@ -139,6 +139,7 @@ Windows Service
139139
.. _docutils: https://docutils.sourceforge.io/
140140
.. _markdown: https://python-markdown.github.io/
141141
.. _markdown2: https://github.com/trentm/python-markdown2
142+
.. _mistune: https://github.com/lepture/mistune
142143

143144

144145
Getting Roundup

roundup/cgi/templating.py

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,49 @@
5454
except ImportError:
5555
ReStructuredText = None
5656
try:
57-
from markdown2 import markdown
57+
from itertools import zip_longest
5858
except ImportError:
59+
from itertools import izip_longest as zip_longest
60+
61+
def _import_markdown2():
5962
try:
60-
from markdown import markdown
63+
import markdown2, re
64+
class Markdown(markdown2.Markdown):
65+
# don't restrict protocols in links
66+
_safe_protocols = re.compile('', re.IGNORECASE)
67+
68+
markdown = lambda s: Markdown(safe_mode='escape', extras={ 'fenced-code-blocks' : True }).convert(s)
6169
except ImportError:
6270
markdown = None
63-
try:
64-
from itertools import zip_longest
65-
except ImportError:
66-
from itertools import izip_longest as zip_longest
71+
72+
return markdown
73+
74+
def _import_markdown():
75+
try:
76+
from markdown import markdown as markdown_impl
77+
from markdown.extensions import Extension as MarkdownExtension
78+
79+
# make sure any HTML tags get escaped
80+
class EscapeHtml(MarkdownExtension):
81+
def extendMarkdown(self, md):
82+
md.preprocessors.deregister('html_block')
83+
md.inlinePatterns.deregister('html')
84+
85+
markdown = lambda s: markdown_impl(s, extensions=[EscapeHtml(), 'fenced_code'])
86+
except ImportError:
87+
markdown = None
88+
89+
return markdown
90+
91+
def _import_mistune():
92+
try:
93+
from mistune import markdown
94+
except ImportError:
95+
markdown = None
96+
97+
return markdown
98+
99+
markdown = _import_markdown2() or _import_markdown() or _import_mistune()
67100

68101
# bring in the templating support
69102
from roundup.cgi import TranslationService, ZTUtils

test/test_templating.py

Lines changed: 127 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,36 @@
99
import pytest
1010
from .pytest_patcher import mark_class
1111

12-
try:
13-
from docutils.core import publish_parts as ReStructuredText
12+
if ReStructuredText:
1413
skip_rst = lambda func, *args, **kwargs: func
15-
except ImportError:
16-
ReStructuredText = None
14+
else:
1715
skip_rst = mark_class(pytest.mark.skip(
1816
reason='ReStructuredText not available'))
1917

20-
try:
21-
from StructuredText.StructuredText import HTML as StructuredText
18+
if StructuredText:
2219
skip_stext = lambda func, *args, **kwargs: func
23-
except ImportError:
24-
try: # older version
25-
import StructuredText
26-
skip_stext = lambda func, *args, **kwargs: func
27-
except ImportError:
28-
StructuredText = None
29-
skip_stext = mark_class(pytest.mark.skip(
30-
reason='StructuredText not available'))
31-
32-
try:
20+
else:
21+
skip_stext = mark_class(pytest.mark.skip(
22+
reason='StructuredText not available'))
23+
24+
import roundup.cgi.templating
25+
if roundup.cgi.templating._import_mistune():
26+
skip_mistune = lambda func, *args, **kwargs: func
27+
else:
28+
skip_mistune = mark_class(pytest.mark.skip(
29+
reason='mistune not available'))
30+
31+
if roundup.cgi.templating._import_markdown2():
32+
skip_markdown2 = lambda func, *args, **kwargs: func
33+
else:
34+
skip_markdown2 = mark_class(pytest.mark.skip(
35+
reason='markdown2 not available'))
36+
37+
if roundup.cgi.templating._import_markdown():
3338
skip_markdown = lambda func, *args, **kwargs: func
34-
from markdown2 import markdown
35-
except ImportError:
36-
try:
37-
from markdown import markdown
38-
except ImportError:
39-
markdown = None
40-
skip_markdown = mark_class(pytest.mark.skip(
41-
reason='markdown not available'))
39+
else:
40+
skip_markdown = mark_class(pytest.mark.skip(
41+
reason='markdown not available'))
4242

4343
from roundup.anypy.strings import u2s, s2u
4444

@@ -247,21 +247,7 @@ def test_string_plain_or_hyperlinked(self):
247247

248248
self.assertEqual(p.hyperlinked(), 'A string &lt;b&gt; with <a href="mailto:[email protected]">[email protected]</a> embedded &amp;lt; html&lt;/b&gt;')
249249

250-
@skip_markdown
251-
def test_string_markdown_installed(self):
252-
pass # just so we have a record of a skipped test
253-
254-
def test_string_markdown(self):
255-
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'A string http://localhost with [email protected] *embedded* \u00df'))
256-
if markdown:
257-
self.assertEqual(p.markdown().strip(), u2s(u'<p>A string <a href="http://localhost">http://localhost</a> with <a href="mailto:[email protected]">[email protected]</a> <em>embedded</em> \u00df</p>'))
258-
else:
259-
self.assertEqual(p.markdown(), u2s(u'A string <a href="http://localhost" rel="nofollow noopener">http://localhost</a> with <a href="mailto:[email protected]">[email protected]</a> *embedded* \u00df'))
260-
261250
@skip_rst
262-
def test_string_rst_installed(self):
263-
pass # just so we have a record of a skipped test
264-
265251
def test_string_rst(self):
266252
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'A string with [email protected] *embedded* \u00df'))
267253

@@ -295,23 +281,14 @@ def test_string_rst(self):
295281
</div>
296282
</div>
297283
'''
298-
if ReStructuredText:
299-
self.assertEqual(p.rst(), u2s(u'<div class="document">\n<p>A string with <a class="reference external" href="mailto:cmeerw&#64;example.com">cmeerw&#64;example.com</a> <em>embedded</em> \u00df</p>\n</div>\n'))
300-
self.assertEqual(q.rst(), u2s(q_result))
301-
self.assertEqual(r.rst(), u2s(r_result))
302-
else:
303-
self.assertEqual(p.rst(), u2s(u'A string with <a href="mailto:[email protected]">[email protected]</a> *embedded* \u00df'))
284+
self.assertEqual(p.rst(), u2s(u'<div class="document">\n<p>A string with <a class="reference external" href="mailto:cmeerw&#64;example.com">cmeerw&#64;example.com</a> <em>embedded</em> \u00df</p>\n</div>\n'))
285+
self.assertEqual(q.rst(), u2s(q_result))
286+
self.assertEqual(r.rst(), u2s(r_result))
304287

305288
@skip_stext
306-
def test_string_stext_installed(self):
307-
pass # just so we have a record of a skipped test
308-
309289
def test_string_stext(self):
310290
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'A string with [email protected] *embedded* \u00df'))
311-
if StructuredText:
312-
self.assertEqual(p.stext(), u2s(u'<p>A string with <a href="mailto:[email protected]">[email protected]</a> <em>embedded</em> \u00df</p>\n'))
313-
else:
314-
self.assertEqual(p.stext(), u2s(u'A string with <a href="mailto:[email protected]">[email protected]</a> *embedded* \u00df'))
291+
self.assertEqual(p.stext(), u2s(u'<p>A string with <a href="mailto:[email protected]">[email protected]</a> <em>embedded</em> \u00df</p>\n'))
315292

316293
def test_string_field(self):
317294
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', 'A string <b> with [email protected] embedded &lt; html</b>')
@@ -436,6 +413,105 @@ def test_input_xhtml(self):
436413
input=input_xhtml(**attrs)
437414
self.assertEqual(input, '<input class="required" disabled="disabled" size="30" type="text"/>')
438415

416+
# common markdown test cases
417+
class MarkdownTests:
418+
def test_string_markdown(self):
419+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'A string http://localhost with [email protected] <br> *embedded* \u00df'))
420+
self.assertEqual(p.markdown().strip(), u2s(u'<p>A string <a href="http://localhost">http://localhost</a> with <a href="mailto:[email protected]">[email protected]</a> &lt;br&gt; <em>embedded</em> \u00df</p>'))
421+
422+
def test_string_markdown_code_block(self):
423+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'embedded code block\n\n```\nline 1\nline 2\n```\n\nnew paragraph'))
424+
self.assertEqual(p.markdown().strip().replace('\n\n', '\n'), u2s(u'<p>embedded code block</p>\n<pre><code>line 1\nline 2\n</code></pre>\n<p>new paragraph</p>'))
425+
426+
@skip_mistune
427+
class MistuneTestCase(TemplatingTestCase, MarkdownTests) :
428+
def setUp(self):
429+
TemplatingTestCase.setUp(self)
430+
431+
from roundup.cgi import templating
432+
self.__markdown = templating.markdown
433+
templating.markdown = templating._import_mistune()
434+
435+
def tearDown(self):
436+
from roundup.cgi import templating
437+
templating.markdown = self.__markdown
438+
439+
@skip_markdown2
440+
class Markdown2TestCase(TemplatingTestCase, MarkdownTests) :
441+
def setUp(self):
442+
TemplatingTestCase.setUp(self)
443+
444+
from roundup.cgi import templating
445+
self.__markdown = templating.markdown
446+
templating.markdown = templating._import_markdown2()
447+
448+
def tearDown(self):
449+
from roundup.cgi import templating
450+
templating.markdown = self.__markdown
451+
452+
@skip_markdown
453+
class MarkdownTestCase(TemplatingTestCase, MarkdownTests) :
454+
def setUp(self):
455+
TemplatingTestCase.setUp(self)
456+
457+
from roundup.cgi import templating
458+
self.__markdown = templating.markdown
459+
templating.markdown = templating._import_markdown()
460+
461+
def tearDown(self):
462+
from roundup.cgi import templating
463+
templating.markdown = self.__markdown
464+
465+
466+
class NoMarkdownTestCase(TemplatingTestCase) :
467+
def setUp(self):
468+
TemplatingTestCase.setUp(self)
469+
470+
from roundup.cgi import templating
471+
self.__markdown = templating.markdown
472+
templating.markdown = None
473+
474+
def tearDown(self):
475+
from roundup.cgi import templating
476+
templating.markdown = self.__markdown
477+
478+
def test_string_markdown(self):
479+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'A string http://localhost with [email protected] <br> *embedded* \u00df'))
480+
self.assertEqual(p.markdown(), u2s(u'A string <a href="http://localhost" rel="nofollow noopener">http://localhost</a> with <a href="mailto:[email protected]">[email protected]</a> &lt;br&gt; *embedded* \u00df'))
481+
482+
class NoRstTestCase(TemplatingTestCase) :
483+
def setUp(self):
484+
TemplatingTestCase.setUp(self)
485+
486+
from roundup.cgi import templating
487+
self.__ReStructuredText = templating.ReStructuredText
488+
templating.ReStructuredText = None
489+
490+
def tearDown(self):
491+
from roundup.cgi import templating
492+
templating.ReStructuredText = self.__ReStructuredText
493+
494+
def test_string_rst(self):
495+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'A string with [email protected] *embedded* \u00df'))
496+
self.assertEqual(p.rst(), u2s(u'A string with <a href="mailto:[email protected]">[email protected]</a> *embedded* \u00df'))
497+
498+
class NoStextTestCase(TemplatingTestCase) :
499+
def setUp(self):
500+
TemplatingTestCase.setUp(self)
501+
502+
from roundup.cgi import templating
503+
self.__StructuredText = templating.StructuredText
504+
templating.StructuredText = None
505+
506+
def tearDown(self):
507+
from roundup.cgi import templating
508+
templating.StructuredText = self.__StructuredText
509+
510+
def test_string_stext(self):
511+
p = StringHTMLProperty(self.client, 'test', '1', None, 'test', u2s(u'A string with [email protected] *embedded* \u00df'))
512+
self.assertEqual(p.stext(), u2s(u'A string with <a href="mailto:[email protected]">[email protected]</a> *embedded* \u00df'))
513+
514+
439515
r'''
440516
class HTMLPermissions:
441517
def is_edit_ok(self):

0 commit comments

Comments
 (0)