Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fixing problem with links inside of < > brackets in safe mode. #72

Merged
merged 15 commits into from

3 participants

@mdirolf

There's a test case that better explains what was going wrong.

Also, a fix for an issue introduced with the logic inversion in commit 35930e0...

mdirolf and others added some commits
@mdirolf mdirolf attempt at a fix for issue w/ MD links inside of html tagish stuff wi…
…th safe mode on.
425fde1
@startling startling New footnotes configuration option: BACKLINK_TEXT (second try).
BACKLINK_TEXT specifies the text that's used in the link at the end of
the footnote to link back up to the reader's place. It still defaults to
"&#8617;".

Okay, so at first I had an uncessarily complicated commit for this and
submitted a pull request. Waylan showed me a better way to do it, here:

startling@ee7d1a2#markdown/extensions/footnotes.py-P19

So I made another commit and added it to the pull request. But then I
accidentally added yet another commit to the pull request, accidentally.
Since then, I've realized it would be best to start with a new branch
and closed that first pull request.

Hopefully this will be the last try.
1a291cf
@startling startling fixed an error in the BACKLINK_TEXT option in the footnotes extension.
I accidentally changed the wrong line (L294 instead of L293) to
"self.footnotes.getConfig("BACKLINK_TEXT")" before. This fixes that.
e5188ca
@waylan Fixed issue #66. Silly error. Not sure why the shebang lines were cap…
…italized. Thanks for the report.
4366483
@waylan Fixed #60. When we updated codehilite, we forgot to update fenced_cod…
…ee to work with it.
12baab2
@waylan Fixed #69. url_sanitize no longer crashes on unparsable urls.
Also optimized the code to bypass parsing when not in safe_mode and return
immediately upon failure rather than continue parsing when in safe_mode.

Note that in Python2.7+ more urls may fail than in older versions because
IPv6 support was added to urlparse and it apparently mistakenly identifies some
urls as IPv6 when they are not. Seeing this only applies to safe_mode now,
I don't really care.
35930e0
@waylan Fixed #61. stdin and stdout should work better in python 3.
Apparently, in Python3 stdin and stdout take str (unicode) not bytes.
This provides a solution that will work in both python 2 & 3.
f395930
@waylan Fixed #70. Empty anglebrackets '<>' are now properly recognized as ra…
…w html.
5f959a2
@waylan Fixed #57. Multiline HTML Blocks no longer require a blank line after…
… them.
44caa3a
@waylan Fixed #68. Blank line is not required after html comments.
Interestingly, the change to the misc/mismatched-tags test is inline with
PHP Markdown Extra's behavior  but not markdown.pl, which produces invalid html.
5d177e6
@mdirolf mdirolf minor: adding some common OSX / emacs gitignore patterns e5d4907
@mdirolf mdirolf Fix logic bug introduced in 35930e0... 542324b
@mdirolf mdirolf When safe mode is 'escape', don't allow bad html to stop further proc…
…essing.

See tests/html4_safe/html_then_blockquote.(txt|html).

It looks like having unclosed block-level html elements was causing
further processing not to happen, even in the case where we're
escaping HTML. Since we're escaping HTML, it seems like it shouldn't
affect processing at all. This changes output results in a couple
of other tests, but the new output seems reasonable to me.
a2377e1
@mdirolf

Just added another commit - a2377e1 - that fixes an issue where unclosed block-level html elements would stop further processing of markdown. This seems like correct behavior when passing-through html, but not when escaping it. The change turns off html processing when safe mode is set to 'escape'. In that case I think we just want to escape the text and be done with it.

Feedback?

@waylan
Owner

Hey mdrilf,

Thanks for the work on this. However, in the future, could you keep unrelated issues in separate pull requests? Some of the stuff here is a no-brainier, but others I need to look at closer. Everything all grouped together makes that harder to do. I guess what I'm trying to say is that less work for me gets your requests merged faster.

I'll need to play with 425fde1 and compare with other implementations. Babelmark makes this fairly easy.

Regarding 542324b, is that just a documentation edit? Was the actual logic changed previously, Should it not have been?

a2377e1 is an interesting solution I hadn't considered. IIRC, the current behavior is there for historical reasons. We didn't always use ElementTree (which gives us escaping for free). No reason why we can't take advantage of that now. Question though, based on your commit message, is sounds like your patch is just masking a bug in the raw html parser which will still manifest itself when we don't use safe_mode='escape'. If so, we need to fix that. Could you provide more info (preferably in a separate bug report).

I'll probably just merge your commits on the fenced code blocks, but those really should be part of issue #53. At least those bugs will be fixed while waiting for the refactor and the tests will help in doing the refactor as well.

@mdirolf
@waylan
Owner
@mdirolf
@waylan
Owner
@mdirolf
@waylan waylan merged commit 69b365d into waylan:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 13, 2012
  1. @mdirolf
Commits on Jan 14, 2012
  1. @startling @mdirolf

    New footnotes configuration option: BACKLINK_TEXT (second try).

    startling authored mdirolf committed
    BACKLINK_TEXT specifies the text that's used in the link at the end of
    the footnote to link back up to the reader's place. It still defaults to
    "&#8617;".
    
    Okay, so at first I had an uncessarily complicated commit for this and
    submitted a pull request. Waylan showed me a better way to do it, here:
    
    startling@ee7d1a2#markdown/extensions/footnotes.py-P19
    
    So I made another commit and added it to the pull request. But then I
    accidentally added yet another commit to the pull request, accidentally.
    Since then, I've realized it would be best to start with a new branch
    and closed that first pull request.
    
    Hopefully this will be the last try.
  2. @startling @mdirolf

    fixed an error in the BACKLINK_TEXT option in the footnotes extension.

    startling authored mdirolf committed
    I accidentally changed the wrong line (L294 instead of L293) to
    "self.footnotes.getConfig("BACKLINK_TEXT")" before. This fixes that.
  3. @mdirolf

    Fixed issue #66. Silly error. Not sure why the shebang lines were cap…

    authored mdirolf committed
    …italized. Thanks for the report.
  4. @mdirolf

    Fixed #60. When we updated codehilite, we forgot to update fenced_cod…

    authored mdirolf committed
    …ee to work with it.
  5. @mdirolf

    Fixed #69. url_sanitize no longer crashes on unparsable urls.

    authored mdirolf committed
    Also optimized the code to bypass parsing when not in safe_mode and return
    immediately upon failure rather than continue parsing when in safe_mode.
    
    Note that in Python2.7+ more urls may fail than in older versions because
    IPv6 support was added to urlparse and it apparently mistakenly identifies some
    urls as IPv6 when they are not. Seeing this only applies to safe_mode now,
    I don't really care.
  6. @mdirolf

    Fixed #61. stdin and stdout should work better in python 3.

    authored mdirolf committed
    Apparently, in Python3 stdin and stdout take str (unicode) not bytes.
    This provides a solution that will work in both python 2 & 3.
  7. @mdirolf

    Fixed #70. Empty anglebrackets '<>' are now properly recognized as ra…

    authored mdirolf committed
    …w html.
  8. @mdirolf

    Fixed #57. Multiline HTML Blocks no longer require a blank line after…

    authored mdirolf committed
    … them.
  9. @mdirolf

    Fixed #68. Blank line is not required after html comments.

    authored mdirolf committed
    Interestingly, the change to the misc/mismatched-tags test is inline with
    PHP Markdown Extra's behavior  but not markdown.pl, which produces invalid html.
  10. @mdirolf
  11. @mdirolf
  12. @mdirolf

    When safe mode is 'escape', don't allow bad html to stop further proc…

    mdirolf authored
    …essing.
    
    See tests/html4_safe/html_then_blockquote.(txt|html).
    
    It looks like having unclosed block-level html elements was causing
    further processing not to happen, even in the case where we're
    escaping HTML. Since we're escaping HTML, it seems like it shouldn't
    affect processing at all. This changes output results in a couple
    of other tests, but the new output seems reasonable to me.
Commits on Jan 17, 2012
  1. @mdirolf
  2. @mdirolf
This page is out of date. Refresh to see the latest.
Showing with 286 additions and 78 deletions.
  1. +2 −1  .gitignore
  2. +25 −15 markdown/__init__.py
  3. +1 −1  markdown/__main__.py
  4. +1 −1  markdown/extensions/def_list.py
  5. +6 −6 markdown/extensions/fenced_code.py
  6. +8 −2 markdown/extensions/footnotes.py
  7. +1 −1  markdown/extensions/tables.py
  8. +18 −9 markdown/inlinepatterns.py
  9. +5 −2 markdown/postprocessors.py
  10. +19 −15 markdown/preprocessors.py
  11. +32 −0 tests/extensions/fenced_code.html
  12. +34 −0 tests/extensions/fenced_code.txt
  13. +32 −0 tests/extensions/github_flavored.html
  14. +34 −0 tests/extensions/github_flavored.txt
  15. +6 −0 tests/extensions/test.cfg
  16. +6 −0 tests/html4_safe/html_then_blockquote.html
  17. +6 −0 tests/html4_safe/html_then_blockquote.txt
  18. +1 −0  tests/html4_safe/link.html
  19. +1 −0  tests/html4_safe/link.txt
  20. +3 −0  tests/html4_safe/test.cfg
  21. +5 −1 tests/misc/comments.html
  22. +3 −0  tests/misc/comments.txt
  23. +4 −1 tests/misc/mismatched-tags.html
  24. +9 −1 tests/misc/multi-line-tags.html
  25. +7 −0 tests/misc/multi-line-tags.txt
  26. +1 −1  tests/misc/multiline-comments.html
  27. +7 −4 tests/safe_mode/inline-html-simple.html
  28. +9 −17 tests/safe_mode/script_tags.html
View
3  .gitignore
@@ -1,3 +1,4 @@
+.DS*
*.pyc
*.bak
*.swp
@@ -8,4 +9,4 @@ tmp/*
MANIFEST
.venv
*~
-#*
+*#*
View
40 markdown/__init__.py
@@ -333,28 +333,38 @@ def convertFile(self, input=None, output=None, encoding=None):
encoding = encoding or "utf-8"
# Read the source
- if isinstance(input, basestring):
- input_file = codecs.open(input, mode="r", encoding=encoding)
+ if input:
+ if isinstance(input, str):
+ input_file = codecs.open(input, mode="r", encoding=encoding)
+ else:
+ input_file = codecs.getreader(encoding)(input)
+ text = input_file.read()
+ input_file.close()
else:
- input = input or sys.stdin
- input_file = codecs.getreader(encoding)(input)
- text = input_file.read()
- input_file.close()
- text = text.lstrip(u'\ufeff') # remove the byte-order mark
+ text = sys.stdin.read()
+ if not isinstance(text, unicode):
+ text = text.decode(encoding)
+
+ text = text.lstrip('\ufeff') # remove the byte-order mark
# Convert
html = self.convert(text)
# Write to file or stdout
- if isinstance(output, basestring):
- output_file = codecs.open(output, "w",
- encoding=encoding,
- errors="xmlcharrefreplace")
- output_file.write(html)
- output_file.close()
+ if output:
+ if isinstance(output, str):
+ output_file = codecs.open(output, "w",
+ encoding=encoding,
+ errors="xmlcharrefreplace")
+ output_file.write(html)
+ output_file.close()
+ else:
+ writer = codecs.getwriter(encoding)
+ output_file = writer(output, errors="xmlcharrefreplace")
+ output_file.write(html)
+ # Don't close here. User may want to write more.
else:
- output = output or sys.stdout
- output.write(html.encode(encoding, "xmlcharrefreplace"))
+ sys.stdout.write(html)
return self
View
2  markdown/__main__.py
@@ -53,7 +53,7 @@ def parse_options():
(options, args) = parser.parse_args()
if len(args) == 0:
- input_file = sys.stdin
+ input_file = None
else:
input_file = args[0]
View
2  markdown/extensions/def_list.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env Python
+#!/usr/bin/env python
"""
Definition List Extension for Python-Markdown
=============================================
View
12 markdown/extensions/fenced_code.py
@@ -81,7 +81,7 @@
# Global vars
FENCED_BLOCK_RE = re.compile( \
- r'(?P<fence>^(?:~{3,}|`{3,}))[ ]*(\{?\.(?P<lang>[a-zA-Z0-9_-]*)\}?)?[ ]*\n(?P<code>.*?)(?P=fence)[ ]*$',
+ r'(?P<fence>^(?:~{3,}|`{3,}))[ ]*(\{?\.?(?P<lang>[a-zA-Z0-9_-]*)\}?)?[ ]*\n(?P<code>.*?)(?<=\n)(?P=fence)[ ]*$',
re.MULTILINE|re.DOTALL
)
CODE_WRAP = '<pre><code%s>%s</code></pre>'
@@ -130,12 +130,12 @@ def run(self, lines):
# is enabled, so we call it to highlite the code
if self.codehilite_conf:
highliter = CodeHilite(m.group('code'),
- linenos=self.codehilite_conf['force_linenos'],
- guess_lang=self.codehilite_conf['guess_lang'],
- css_class=self.codehilite_conf['css_class'],
- style=self.codehilite_conf['pygments_style'],
+ linenos=self.codehilite_conf['force_linenos'][0],
+ guess_lang=self.codehilite_conf['guess_lang'][0],
+ css_class=self.codehilite_conf['css_class'][0],
+ style=self.codehilite_conf['pygments_style'][0],
lang=(m.group('lang') or None),
- noclasses=self.codehilite_conf['noclasses'])
+ noclasses=self.codehilite_conf['noclasses'][0])
code = highliter.hilite()
else:
View
10 markdown/extensions/footnotes.py
@@ -43,7 +43,11 @@ def __init__ (self, configs):
'UNIQUE_IDS':
[False,
"Avoid name collisions across "
- "multiple calls to reset()."]}
+ "multiple calls to reset()."],
+ "BACKLINK_TEXT":
+ ["&#8617;",
+ "The text string that links from the footnote to the reader's place."]
+ }
for key, value in configs:
self.config[key][0] = value
@@ -282,9 +286,11 @@ def run(self, root):
class FootnotePostprocessor(markdown.postprocessors.Postprocessor):
""" Replace placeholders with html entities. """
+ def __init__(self, footnotes):
+ self.footnotes = footnotes
def run(self, text):
- text = text.replace(FN_BACKLINK_TEXT, "&#8617;")
+ text = text.replace(FN_BACKLINK_TEXT, self.footnotes.getConfig("BACKLINK_TEXT"))
return text.replace(NBSP_PLACEHOLDER, "&#160;")
def makeExtension(configs=[]):
View
2  markdown/extensions/tables.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env Python
+#!/usr/bin/env python
"""
Tables Extension for Python-Markdown
====================================
View
27 markdown/inlinepatterns.py
@@ -311,20 +311,29 @@ def sanitize_url(self, url):
`username:password@host:port`.
"""
+ if not self.markdown.safeMode:
+ # Return immediately bipassing parsing.
+ return url
+
+ try:
+ scheme, netloc, path, params, query, fragment = url = urlparse(url)
+ except ValueError:
+ # Bad url - so bad it couldn't be parsed.
+ return ''
+
locless_schemes = ['', 'mailto', 'news']
- scheme, netloc, path, params, query, fragment = url = urlparse(url)
- safe_url = False
- if netloc != '' or scheme in locless_schemes:
- safe_url = True
+ if netloc == '' and scheme not in locless_schemes:
+ # This fails regardless of anything else.
+ # Return immediately to save additional proccessing
+ return ''
for part in url[2:]:
if ":" in part:
- safe_url = False
+ # Not a safe url
+ return ''
- if self.markdown.safeMode and not safe_url:
- return ''
- else:
- return urlunparse(url)
+ # Url passes all tests. Return url as-is.
+ return urlunparse(url)
class ImagePattern(LinkPattern):
""" Return a img element from the given match. """
View
7 markdown/postprocessors.py
@@ -49,7 +49,6 @@ def run(self, text):
""" Iterate over html stash and restore "safe" html. """
for i in range(self.markdown.htmlStash.html_counter):
html, safe = self.markdown.htmlStash.rawHtmlBlocks[i]
- html = self.unescape(html)
if self.markdown.safeMode and not safe:
if str(self.markdown.safeMode).lower() == 'escape':
html = self.escape(html)
@@ -61,6 +60,7 @@ def run(self, text):
text = text.replace("<p>%s</p>" %
(self.markdown.htmlStash.get_placeholder(i)),
html + "\n")
+ html = self.unescape(html)
text = text.replace(self.markdown.htmlStash.get_placeholder(i),
html)
return text
@@ -69,7 +69,10 @@ def unescape(self, html):
""" Unescape any markdown escaped text within inline html. """
for k, v in self.markdown.treeprocessors['inline'].stashed_nodes.items():
ph = util.INLINE_PLACEHOLDER % k
- html = html.replace(ph, '\%s' % v)
+ try:
+ html = html.replace(ph, '%s' % util.etree.tostring(v))
+ except:
+ html = html.replace(ph, '\%s' % v)
return html
def escape(self, html):
View
34 markdown/preprocessors.py
@@ -14,7 +14,8 @@
def build_preprocessors(md_instance, **kwargs):
""" Build the default set of preprocessors used by Markdown. """
preprocessors = odict.OrderedDict()
- preprocessors["html_block"] = HtmlBlockPreprocessor(md_instance)
+ if md_instance.safeMode != 'escape':
+ preprocessors["html_block"] = HtmlBlockPreprocessor(md_instance)
preprocessors["reference"] = ReferencePreprocessor(md_instance)
return preprocessors
@@ -78,7 +79,7 @@ def _get_left_tag(self, block):
attrs[ma.group('attr2').strip()] = ""
return tag, len(m.group(0)), attrs
else:
- tag = block[1:].replace(">", " ", 1).split()[0].lower()
+ tag = block[1:].split(">", 1)[0].lower()
return tag, len(tag)+2, {}
def _recursive_tagfind(self, ltag, rtag, start_index, block):
@@ -143,21 +144,20 @@ def run(self, lines):
if not in_tag:
if block.startswith("<") and len(block.strip()) > 1:
- left_tag, left_index, attrs = self._get_left_tag(block)
- right_tag, data_index = self._get_right_tag(left_tag,
- left_index,
- block)
if block[1] == "!":
# is a comment block
- left_tag = "--"
- right_tag, data_index = self._get_right_tag(left_tag,
- left_index,
- block)
- # keep checking conditions below and maybe just append
+ left_tag, left_index, attrs = "--", 2, ()
+ else:
+ left_tag, left_index, attrs = self._get_left_tag(block)
+ right_tag, data_index = self._get_right_tag(left_tag,
+ left_index,
+ block)
+ # keep checking conditions below and maybe just append
if data_index < len(block) \
- and util.isBlockLevel(left_tag):
+ and (util.isBlockLevel(left_tag)
+ or left_tag == '--'):
text.insert(0, block[data_index:])
block = block[:data_index]
@@ -204,12 +204,16 @@ def run(self, lines):
else:
items.append(block)
- right_tag, data_index = self._get_right_tag(left_tag,
- left_index,
- block)
+ right_tag, data_index = self._get_right_tag(left_tag, 0, block)
if self._equal_tags(left_tag, right_tag):
# if find closing tag
+
+ if data_index < len(block):
+ # we have more text after right_tag
+ items[-1] = block[:data_index]
+ text.insert(0, block[data_index:])
+
in_tag = False
if self.markdown_in_raw and 'markdown' in attrs.keys():
start = re.sub(r'\smarkdown(=[\'"]?[^> ]*[\'"]?)?',
View
32 tests/extensions/fenced_code.html
@@ -0,0 +1,32 @@
+<p>index 0000000..6e956a9</p>
+<pre><code>--- /dev/null
++++ b/test/data/stripped_text/mike-30-lili
+@@ -0,0 +1,27 @@
++Summary:
++ drift_mod.py | 1 +
++ 1 files changed, 1 insertions(+), 0 deletions(-)
++
++commit da4bfb04debdd994683740878d09988b2641513d
++Author: Mike Dirolf &lt;mike@dirolf.com&gt;
++Date: Tue Jan 17 13:42:28 2012 -0500
++
++```
++minor: just wanted to push something.
++```
++
++diff --git a/drift_mod.py b/drift_mod.py
++index 34dfba6..8a88a69 100644
++
++```
++--- a/drift_mod.py
+++++ b/drift_mod.py
++@@ -281,6 +281,7 @@ CONTEXT_DIFF_LINE_PATTERN = re.compile(r'^('
++ '|\+ .*'
++ '|- .*'
++ ')$')
+++
++ def wrap_context_diffs(message_text):
++ return _wrap_diff(CONTEXT_DIFF_HEADER_PATTERN,
++ CONTEXT_DIFF_LINE_PATTERN,
++```
+</code></pre>
View
34 tests/extensions/fenced_code.txt
@@ -0,0 +1,34 @@
+index 0000000..6e956a9
+
+```
+--- /dev/null
++++ b/test/data/stripped_text/mike-30-lili
+@@ -0,0 +1,27 @@
++Summary:
++ drift_mod.py | 1 +
++ 1 files changed, 1 insertions(+), 0 deletions(-)
++
++commit da4bfb04debdd994683740878d09988b2641513d
++Author: Mike Dirolf <mike@dirolf.com>
++Date: Tue Jan 17 13:42:28 2012 -0500
++
++```
++minor: just wanted to push something.
++```
++
++diff --git a/drift_mod.py b/drift_mod.py
++index 34dfba6..8a88a69 100644
++
++```
++--- a/drift_mod.py
+++++ b/drift_mod.py
++@@ -281,6 +281,7 @@ CONTEXT_DIFF_LINE_PATTERN = re.compile(r'^('
++ '|\+ .*'
++ '|- .*'
++ ')$')
+++
++ def wrap_context_diffs(message_text):
++ return _wrap_diff(CONTEXT_DIFF_HEADER_PATTERN,
++ CONTEXT_DIFF_LINE_PATTERN,
++```
+```
View
32 tests/extensions/github_flavored.html
@@ -0,0 +1,32 @@
+<p>index 0000000..6e956a9</p>
+<div class="codehilite"><pre><span class="gd">--- /dev/null</span>
+<span class="gi">+++ b/test/data/stripped_text/mike-30-lili</span>
+<span class="gu">@@ -0,0 +1,27 @@</span>
+<span class="gi">+Summary:</span>
+<span class="gi">+ drift_mod.py | 1 +</span>
+<span class="gi">+ 1 files changed, 1 insertions(+), 0 deletions(-)</span>
+<span class="gi">+</span>
+<span class="gi">+commit da4bfb04debdd994683740878d09988b2641513d</span>
+<span class="gi">+Author: Mike Dirolf &lt;mike@dirolf.com&gt;</span>
+<span class="gi">+Date: Tue Jan 17 13:42:28 2012 -0500</span>
+<span class="gi">+</span>
+<span class="gi">+```</span>
+<span class="gi">+minor: just wanted to push something.</span>
+<span class="gi">+```</span>
+<span class="gi">+</span>
+<span class="gi">+diff --git a/drift_mod.py b/drift_mod.py</span>
+<span class="gi">+index 34dfba6..8a88a69 100644</span>
+<span class="gi">+</span>
+<span class="gi">+```</span>
+<span class="gi">+--- a/drift_mod.py</span>
+<span class="gi">++++ b/drift_mod.py</span>
+<span class="gi">+@@ -281,6 +281,7 @@ CONTEXT_DIFF_LINE_PATTERN = re.compile(r&#39;^(&#39;</span>
+<span class="gi">+ &#39;|\+ .*&#39;</span>
+<span class="gi">+ &#39;|- .*&#39;</span>
+<span class="gi">+ &#39;)$&#39;)</span>
+<span class="gi">++</span>
+<span class="gi">+ def wrap_context_diffs(message_text):</span>
+<span class="gi">+ return _wrap_diff(CONTEXT_DIFF_HEADER_PATTERN,</span>
+<span class="gi">+ CONTEXT_DIFF_LINE_PATTERN,</span>
+<span class="gi">+```</span>
+</pre></div>
View
34 tests/extensions/github_flavored.txt
@@ -0,0 +1,34 @@
+index 0000000..6e956a9
+
+```diff
+--- /dev/null
++++ b/test/data/stripped_text/mike-30-lili
+@@ -0,0 +1,27 @@
++Summary:
++ drift_mod.py | 1 +
++ 1 files changed, 1 insertions(+), 0 deletions(-)
++
++commit da4bfb04debdd994683740878d09988b2641513d
++Author: Mike Dirolf <mike@dirolf.com>
++Date: Tue Jan 17 13:42:28 2012 -0500
++
++```
++minor: just wanted to push something.
++```
++
++diff --git a/drift_mod.py b/drift_mod.py
++index 34dfba6..8a88a69 100644
++
++```
++--- a/drift_mod.py
+++++ b/drift_mod.py
++@@ -281,6 +281,7 @@ CONTEXT_DIFF_LINE_PATTERN = re.compile(r'^('
++ '|\+ .*'
++ '|- .*'
++ ')$')
+++
++ def wrap_context_diffs(message_text):
++ return _wrap_diff(CONTEXT_DIFF_HEADER_PATTERN,
++ CONTEXT_DIFF_LINE_PATTERN,
++```
+```
View
6 tests/extensions/test.cfg
@@ -20,3 +20,9 @@ extensions=toc
[wikilinks]
extensions=wikilinks
+
+[fenced_code]
+extensions=fenced_code
+
+[github_flavored]
+extensions=codehilite,fenced_code
View
6 tests/html4_safe/html_then_blockquote.html
@@ -0,0 +1,6 @@
+<p>to:</p>
+<p>&lt;td /&gt;&lt;td style=&quot;text-align: center; white-space: nowrap;&quot;&gt;&lt;br /&gt;</p>
+<blockquote>
+<p>3) You don't need to alter all localization files.
+ Adding the new labels to the en_US files will do it.</p>
+</blockquote>
View
6 tests/html4_safe/html_then_blockquote.txt
@@ -0,0 +1,6 @@
+to:
+
+<td /><td style="text-align: center; white-space: nowrap;"><br />
+
+> 3) You don't need to alter all localization files.
+> Adding the new labels to the en_US files will do it.
View
1  tests/html4_safe/link.html
@@ -0,0 +1 @@
+<p>&lt;here <a href="http://gmail.com">gmail.com</a> is a link&gt;</p>
View
1  tests/html4_safe/link.txt
@@ -0,0 +1 @@
+<here [gmail.com](http://gmail.com) is a link>
View
3  tests/html4_safe/test.cfg
@@ -0,0 +1,3 @@
+[DEFAULT]
+output_format=html4
+safe_mode=escape
View
6 tests/misc/comments.html
@@ -2,4 +2,8 @@
<p>X&gt;0</p>
<!-- A comment -->
-<div>as if</div>
+<div>as if</div>
+
+<!-- comment -->
+
+<p><strong>no blank line</strong></p>
View
3  tests/misc/comments.txt
@@ -5,3 +5,6 @@ X>0
<!-- A comment -->
<div>as if</div>
+
+<!-- comment -->
+__no blank line__
View
5 tests/misc/mismatched-tags.html
@@ -6,6 +6,9 @@
<p>And this output</p>
<p><em>Compatible with PHP Markdown Extra 1.2.2 and Markdown.pl1.0.2b8:</em></p>
-<!-- comment --><p><div>text</div><br /></p><br />
+<!-- comment -->
+<p><div>text</div><br /></p>
+
+<p><br /></p>
<p>Should be in p</p>
View
10 tests/misc/multi-line-tags.html
@@ -2,4 +2,12 @@
asdf asdfasd
-</div>
+</div>
+
+<div>
+
+foo bar
+
+</div>
+
+<p>No blank line.</p>
View
7 tests/misc/multi-line-tags.txt
@@ -4,3 +4,10 @@
asdf asdfasd
</div>
+
+<div>
+
+foo bar
+
+</div>
+No blank line.
View
2  tests/misc/multiline-comments.html
@@ -2,7 +2,7 @@
foo
--->
+-->
<p>
View
11 tests/safe_mode/inline-html-simple.html
@@ -29,7 +29,8 @@
<pre><code>&lt;!-- Comment --&gt;
</code></pre>
<p>Just plain comment, with trailing spaces on the line:</p>
-<p>&lt;!-- foo --&gt;</p>
+<p>&lt;!-- foo --&gt; <br />
+</p>
<p>Code:</p>
<pre><code>&lt;hr /&gt;
</code></pre>
@@ -37,9 +38,11 @@
<p>&lt;hr&gt;</p>
<p>&lt;hr/&gt;</p>
<p>&lt;hr /&gt;</p>
-<p>&lt;hr&gt;</p>
-<p>&lt;hr/&gt;</p>
-<p>&lt;hr /&gt;</p>
+<p>&lt;hr&gt; <br />
+</p>
+<p>&lt;hr/&gt;<br />
+</p>
+<p>&lt;hr /&gt; </p>
<p>&lt;hr class=&quot;foo&quot; id=&quot;bar&quot; /&gt;</p>
<p>&lt;hr class=&quot;foo&quot; id=&quot;bar&quot;/&gt;</p>
<p>&lt;hr class=&quot;foo&quot; id=&quot;bar&quot; &gt;</p>
View
26 tests/safe_mode/script_tags.html
@@ -1,13 +1,11 @@
<p>This should be stripped/escaped in safe_mode.</p>
<p>&lt;script&gt;
-alert(&quot;Hello world!&quot;)
+alert("Hello world!")
&lt;/script&gt;</p>
<p>With blank lines.</p>
-<p>&lt;script&gt;
-
-alert(&quot;Hello world!&quot;)
-
-&lt;/script&gt;</p>
+<p>&lt;script&gt;</p>
+<p>alert("Hello world!")</p>
+<p>&lt;/script&gt;</p>
<p>Now with some weirdness</p>
<p><code>&lt;script &lt;!--
alert("Hello world!")
@@ -15,14 +13,8 @@
<p>Try another way.</p>
<p>&lt;script &lt;!--
alert(&quot;Hello world!&quot;)
-&lt;/script &lt;&gt;
-
-This time with blank lines.
-
-&lt;script &lt;!--
-
-alert(&quot;Hello world!&quot;)
-
-&lt;/script &lt;&gt;
-
-</p>
+&lt;/script &lt;&gt;</p>
+<p>This time with blank lines.</p>
+<p>&lt;script &lt;!--</p>
+<p>alert("Hello world!")</p>
+<p>&lt;/script &lt;&gt;</p>
Something went wrong with that request. Please try again.