From fa350e2f303572ee8f9a8302dda45a12288d3d95 Mon Sep 17 00:00:00 2001 From: Erik Romijn Date: Sun, 8 Mar 2015 12:34:55 +0100 Subject: [PATCH] Fixed #24464 -- Made built-in HTML template filter functions escape their input by default. This may cause some backwards compatibility issues, but may also resolve security issues in third party projects that fail to heed warnings in our documentation. Thanks Markus Holtermann for help with tests and docs. --- django/template/defaultfilters.py | 14 ++--- docs/howto/custom-template-tags.txt | 27 +++++++--- docs/releases/1.8.txt | 20 ++++++++ .../template_tests/filter_tests/test_join.py | 12 +++++ .../filter_tests/test_linebreaks.py | 12 +++++ .../filter_tests/test_linebreaksbr.py | 12 +++++ .../filter_tests/test_linenumbers.py | 12 +++++ .../filter_tests/test_unordered_list.py | 51 ++++++++++++++++++- .../filter_tests/test_urlize.py | 26 +++++++--- .../filter_tests/test_urlizetrunc.py | 12 +++++ 10 files changed, 174 insertions(+), 24 deletions(-) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 57a4a7f1f95..fe714593586 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -191,7 +191,7 @@ def iriencode(value): @register.filter(is_safe=True, needs_autoescape=True) @stringfilter -def linenumbers(value, autoescape=None): +def linenumbers(value, autoescape=True): """Displays text with line numbers.""" lines = value.split('\n') # Find the maximum width of the line count, for use with zero padding @@ -353,14 +353,14 @@ def urlencode(value, safe=None): @register.filter(is_safe=True, needs_autoescape=True) @stringfilter -def urlize(value, autoescape=None): +def urlize(value, autoescape=True): """Converts URLs in plain text into clickable links.""" return mark_safe(_urlize(value, nofollow=True, autoescape=autoescape)) @register.filter(is_safe=True, needs_autoescape=True) @stringfilter -def urlizetrunc(value, limit, autoescape=None): +def urlizetrunc(value, limit, autoescape=True): """ Converts URLs into clickable links, truncating URLs to the given character limit, and adding 'rel=nofollow' attribute to discourage spamming. @@ -457,7 +457,7 @@ def force_escape(value): @register.filter("linebreaks", is_safe=True, needs_autoescape=True) @stringfilter -def linebreaks_filter(value, autoescape=None): +def linebreaks_filter(value, autoescape=True): """ Replaces line breaks in plain text with appropriate HTML; a single newline becomes an HTML line break (``
``) and a new line @@ -469,7 +469,7 @@ def linebreaks_filter(value, autoescape=None): @register.filter(is_safe=True, needs_autoescape=True) @stringfilter -def linebreaksbr(value, autoescape=None): +def linebreaksbr(value, autoescape=True): """ Converts all newlines in a piece of plain text to HTML line breaks (``
``). @@ -552,7 +552,7 @@ def first(value): @register.filter(is_safe=True, needs_autoescape=True) -def join(value, arg, autoescape=None): +def join(value, arg, autoescape=True): """ Joins a list with a string, like Python's ``str.join(list)``. """ @@ -622,7 +622,7 @@ def slice_filter(value, arg): @register.filter(is_safe=True, needs_autoescape=True) -def unordered_list(value, autoescape=None): +def unordered_list(value, autoescape=True): """ Recursively takes a self-nested list and returns an HTML unordered list -- WITHOUT opening and closing \n\t', ) + def test_autoescape(self): + self.assertEqual( + unordered_list(['item 1', 'item 2']), + '\t
  • <a>item 1</a>
  • \n\t
  • item 2
  • ', + ) + + def test_autoescape_off(self): + self.assertEqual( + unordered_list(['item 1', 'item 2'], autoescape=False), + '\t
  • item 1
  • \n\t
  • item 2
  • ', + ) + def test_ulitem(self): @python_2_unicode_compatible class ULItem(object): @@ -110,13 +122,48 @@ class FunctionTests(SimpleTestCase): a = ULItem('a') b = ULItem('b') - self.assertEqual(unordered_list([a, b]), '\t
  • ulitem-a
  • \n\t
  • ulitem-b
  • ') + c = ULItem('c') + self.assertEqual( + unordered_list([a, b, c]), + '\t
  • ulitem-a
  • \n\t
  • ulitem-b
  • \n\t
  • ulitem-<a>c</a>
  • ', + ) def item_generator(): yield a yield b + yield c - self.assertEqual(unordered_list(item_generator()), '\t
  • ulitem-a
  • \n\t
  • ulitem-b
  • ') + self.assertEqual( + unordered_list(item_generator()), + '\t
  • ulitem-a
  • \n\t
  • ulitem-b
  • \n\t
  • ulitem-<a>c</a>
  • ', + ) + + def test_ulitem_autoescape_off(self): + @python_2_unicode_compatible + class ULItem(object): + def __init__(self, title): + self.title = title + + def __str__(self): + return 'ulitem-%s' % str(self.title) + + a = ULItem('a') + b = ULItem('b') + c = ULItem('c') + self.assertEqual( + unordered_list([a, b, c], autoescape=False), + '\t
  • ulitem-a
  • \n\t
  • ulitem-b
  • \n\t
  • ulitem-c
  • ', + ) + + def item_generator(): + yield a + yield b + yield c + + self.assertEqual( + unordered_list(item_generator(), autoescape=False), + '\t
  • ulitem-a
  • \n\t
  • ulitem-b
  • \n\t
  • ulitem-c
  • ', + ) @ignore_warnings(category=RemovedInDjango20Warning) def test_legacy(self): diff --git a/tests/template_tests/filter_tests/test_urlize.py b/tests/template_tests/filter_tests/test_urlize.py index 1c55d647514..ee6744e6cbb 100644 --- a/tests/template_tests/filter_tests/test_urlize.py +++ b/tests/template_tests/filter_tests/test_urlize.py @@ -259,27 +259,27 @@ class FunctionTests(SimpleTestCase): #20364 - Check urlize correctly include quotation marks in links """ self.assertEqual( - urlize('before "hi@example.com" afterwards'), + urlize('before "hi@example.com" afterwards', autoescape=False), 'before "hi@example.com" afterwards', ) self.assertEqual( - urlize('before hi@example.com" afterwards'), + urlize('before hi@example.com" afterwards', autoescape=False), 'before hi@example.com" afterwards', ) self.assertEqual( - urlize('before "hi@example.com afterwards'), + urlize('before "hi@example.com afterwards', autoescape=False), 'before "hi@example.com afterwards', ) self.assertEqual( - urlize('before \'hi@example.com\' afterwards'), + urlize('before \'hi@example.com\' afterwards', autoescape=False), 'before \'hi@example.com\' afterwards', ) self.assertEqual( - urlize('before hi@example.com\' afterwards'), + urlize('before hi@example.com\' afterwards', autoescape=False), 'before hi@example.com\' afterwards', ) self.assertEqual( - urlize('before \'hi@example.com afterwards'), + urlize('before \'hi@example.com afterwards', autoescape=False), 'before \'hi@example.com afterwards', ) @@ -288,7 +288,7 @@ class FunctionTests(SimpleTestCase): #20364 - Check urlize copes with commas following URLs in quotes """ self.assertEqual( - urlize('Email us at "hi@example.com", or phone us at +xx.yy'), + urlize('Email us at "hi@example.com", or phone us at +xx.yy', autoescape=False), 'Email us at "hi@example.com", or phone us at +xx.yy', ) @@ -316,3 +316,15 @@ class FunctionTests(SimpleTestCase): def test_non_string_input(self): self.assertEqual(urlize(123), '123') + + def test_autoescape(self): + self.assertEqual( + urlize('foobarbuz'), + 'foo<a href=" google.com ">bar</a>buz', + ) + + def test_autoescape_off(self): + self.assertEqual( + urlize('foobarbuz', autoescape=False), + 'foogoogle.com ">barbuz', + ) diff --git a/tests/template_tests/filter_tests/test_urlizetrunc.py b/tests/template_tests/filter_tests/test_urlizetrunc.py index 95301283694..2b4b18e1f50 100644 --- a/tests/template_tests/filter_tests/test_urlizetrunc.py +++ b/tests/template_tests/filter_tests/test_urlizetrunc.py @@ -78,3 +78,15 @@ class FunctionTests(SimpleTestCase): def test_non_string_input(self): self.assertEqual(urlizetrunc(123, 1), '123') + + def test_autoescape(self): + self.assertEqual( + urlizetrunc('foobarbuz', 10), + 'foo<a href=" google.com ">bar</a>buz', + ) + + def test_autoescape_off(self): + self.assertEqual( + urlizetrunc('foobarbuz', 9, autoescape=False), + 'foogoogle... ">barbuz', + )