Switched {% cycle %} and {% firstof %} tags to auto-escape their variables per deprecation timeline.

refs #17906.
This commit is contained in:
Tim Graham 2014-03-21 13:17:10 -04:00
parent 274048351a
commit 1ea44a3abd
4 changed files with 28 additions and 81 deletions

View File

@ -17,7 +17,6 @@ from django.template.base import (Node, NodeList, Template, Context, Library,
render_value_in_context) render_value_in_context)
from django.template.smartif import IfParser, Literal from django.template.smartif import IfParser, Literal
from django.template.defaultfilters import date from django.template.defaultfilters import date
from django.utils.deprecation import RemovedInDjango18Warning
from django.utils.encoding import force_text, smart_text from django.utils.encoding import force_text, smart_text
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.html import format_html from django.utils.html import format_html
@ -65,11 +64,10 @@ class CsrfTokenNode(Node):
class CycleNode(Node): class CycleNode(Node):
def __init__(self, cyclevars, variable_name=None, silent=False, escape=False): def __init__(self, cyclevars, variable_name=None, silent=False):
self.cyclevars = cyclevars self.cyclevars = cyclevars
self.variable_name = variable_name self.variable_name = variable_name
self.silent = silent self.silent = silent
self.escape = escape # only while the "future" version exists
def render(self, context): def render(self, context):
if self not in context.render_context: if self not in context.render_context:
@ -81,8 +79,6 @@ class CycleNode(Node):
context[self.variable_name] = value context[self.variable_name] = value
if self.silent: if self.silent:
return '' return ''
if not self.escape:
value = mark_safe(value)
return render_value_in_context(value, context) return render_value_in_context(value, context)
@ -107,16 +103,13 @@ class FilterNode(Node):
class FirstOfNode(Node): class FirstOfNode(Node):
def __init__(self, variables, escape=False): def __init__(self, variables):
self.vars = variables self.vars = variables
self.escape = escape # only while the "future" version exists
def render(self, context): def render(self, context):
for var in self.vars: for var in self.vars:
value = var.resolve(context, True) value = var.resolve(context, True)
if value: if value:
if not self.escape:
value = mark_safe(value)
return render_value_in_context(value, context) return render_value_in_context(value, context)
return '' return ''
@ -554,7 +547,7 @@ def comment(parser, token):
@register.tag @register.tag
def cycle(parser, token, escape=False): def cycle(parser, token):
""" """
Cycles among the given strings each time this tag is encountered. Cycles among the given strings each time this tag is encountered.
@ -587,13 +580,6 @@ def cycle(parser, token, escape=False):
{% endfor %} {% endfor %}
""" """
if not escape:
warnings.warn(
"'The `cycle` template tag is changing to escape its arguments; "
"the non-autoescaping version is deprecated. Load it "
"from the `future` tag library to start using the new behavior.",
RemovedInDjango18Warning, stacklevel=2)
# Note: This returns the exact same node on each {% cycle name %} call; # Note: This returns the exact same node on each {% cycle name %} call;
# that is, the node object returned from {% cycle a b c as name %} and the # that is, the node object returned from {% cycle a b c as name %} and the
# one returned from {% cycle name %} are the exact same object. This # one returned from {% cycle name %} are the exact same object. This
@ -640,13 +626,13 @@ def cycle(parser, token, escape=False):
if as_form: if as_form:
name = args[-1] name = args[-1]
values = [parser.compile_filter(arg) for arg in args[1:-2]] values = [parser.compile_filter(arg) for arg in args[1:-2]]
node = CycleNode(values, name, silent=silent, escape=escape) node = CycleNode(values, name, silent=silent)
if not hasattr(parser, '_namedCycleNodes'): if not hasattr(parser, '_namedCycleNodes'):
parser._namedCycleNodes = {} parser._namedCycleNodes = {}
parser._namedCycleNodes[name] = node parser._namedCycleNodes[name] = node
else: else:
values = [parser.compile_filter(arg) for arg in args[1:]] values = [parser.compile_filter(arg) for arg in args[1:]]
node = CycleNode(values, escape=escape) node = CycleNode(values)
return node return node
@ -701,7 +687,7 @@ def do_filter(parser, token):
@register.tag @register.tag
def firstof(parser, token, escape=False): def firstof(parser, token):
""" """
Outputs the first variable passed that is not False, without escaping. Outputs the first variable passed that is not False, without escaping.
@ -735,17 +721,10 @@ def firstof(parser, token, escape=False):
{% endfilter %} {% endfilter %}
""" """
if not escape:
warnings.warn(
"'The `firstof` template tag is changing to escape its arguments; "
"the non-autoescaping version is deprecated. Load it "
"from the `future` tag library to start using the new behavior.",
RemovedInDjango18Warning, stacklevel=2)
bits = token.split_contents()[1:] bits = token.split_contents()[1:]
if len(bits) < 1: if len(bits) < 1:
raise TemplateSyntaxError("'firstof' statement requires at least one argument") raise TemplateSyntaxError("'firstof' statement requires at least one argument")
return FirstOfNode([parser.compile_filter(bit) for bit in bits], escape=escape) return FirstOfNode([parser.compile_filter(bit) for bit in bits])
@register.tag('for') @register.tag('for')

View File

@ -29,6 +29,8 @@ def url(parser, token):
def cycle(parser, token): def cycle(parser, token):
""" """
This is the future version of `cycle` with auto-escaping. This is the future version of `cycle` with auto-escaping.
The deprecation is now complete and this version is no different
from the non-future version so this can be deprecated (#22306)
By default all strings are escaped. By default all strings are escaped.
@ -42,13 +44,15 @@ def cycle(parser, token):
{% cycle var1 var2|safe var3|safe as somecycle %} {% cycle var1 var2|safe var3|safe as somecycle %}
""" """
return defaulttags.cycle(parser, token, escape=True) return defaulttags.cycle(parser, token)
@register.tag @register.tag
def firstof(parser, token): def firstof(parser, token):
""" """
This is the future version of `firstof` with auto-escaping. This is the future version of `firstof` with auto-escaping.
The deprecation is now complete and this version is no different
from the non-future version so this can be deprecated (#22306)
This is equivalent to:: This is equivalent to::
@ -71,4 +75,4 @@ def firstof(parser, token):
{% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %} {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %}
""" """
return defaulttags.firstof(parser, token, escape=True) return defaulttags.firstof(parser, token)

View File

@ -102,13 +102,11 @@ this::
</tr> </tr>
{% endfor %} {% endfor %}
Note that the variables included in the cycle will not be escaped. Any HTML or Variables included in the cycle will be escaped. You can disable auto-escaping
Javascript code contained in the printed variable will be rendered as-is, which with::
could potentially lead to security issues. So either make sure that you trust
their values or use explicit escaping like this::
{% for o in some_list %} {% for o in some_list %}
<tr class="{% filter force_escape %}{% cycle rowvalue1 rowvalue2 %}{% endfilter %}"> <tr class="{% autoescape off %}{% cycle rowvalue1 rowvalue2 %}{% endautoescape
... ...
</tr> </tr>
{% endfor %} {% endfor %}
@ -196,21 +194,6 @@ In this syntax, each value gets interpreted as a literal string, and there's no
way to specify variable values. Or literal commas. Or spaces. Did we mention way to specify variable values. Or literal commas. Or spaces. Did we mention
you shouldn't use this syntax in any new projects? you shouldn't use this syntax in any new projects?
.. versionchanged:: 1.6
To improve safety, future versions of ``cycle`` will automatically escape
their output. You're encouraged to activate this behavior by loading
``cycle`` from the ``future`` template library::
{% load cycle from future %}
When using the ``future`` version, you can disable auto-escaping with::
{% for o in some_list %}
<tr class="{% autoescape off %}{% cycle rowvalue1 rowvalue2 %}{% endautoescape %}">
...
</tr>
{% endfor %}
.. templatetag:: debug .. templatetag:: debug
@ -268,10 +251,8 @@ Sample usage::
firstof firstof
^^^^^^^ ^^^^^^^
Outputs the first argument variable that is not False. This tag does *not* Outputs the first argument variable that is not ``False``. Outputs nothing if
auto-escape variable values. all the passed variables are ``False``.
Outputs nothing if all the passed variables are False.
Sample usage:: Sample usage::
@ -292,32 +273,15 @@ passed variables are False::
{% firstof var1 var2 var3 "fallback value" %} {% firstof var1 var2 var3 "fallback value" %}
Note that currently the variables included in the firstof tag will not be This tag auto-escapes variable values. You can disable auto-escaping with::
escaped. Any HTML or Javascript code contained in the printed variable will be
rendered as-is, which could potentially lead to security issues. If you need
to escape the variables in the firstof tag, you must do so explicitly::
{% filter force_escape %} {% autoescape off %}
{% firstof var1 var2 var3 "fallback value" %} {% firstof var1 var2 var3 "<strong>fallback value</strong>" %}
{% endfilter %} {% endautoescape %}
.. versionchanged:: 1.6 Or if only some variables should be escaped, you can use::
To improve safety, future versions of ``firstof`` will automatically escape {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %}
their output. You're encouraged to activate this behavior by loading
``firstof`` from the ``future`` template library::
{% load firstof from future %}
When using the ``future`` version, you can disable auto-escaping with::
{% autoescape off %}
{% firstof var1 var2 var3 "<strong>fallback value</strong>" %}
{% endautoescape %}
Or if only some variables should be escaped, you can use::
{% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %}
.. templatetag:: for .. templatetag:: for

View File

@ -883,13 +883,13 @@ class TemplateTests(TestCase):
'cycle17': ("{% cycle 'a' 'b' 'c' as abc silent %}{% cycle abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}", {}, ""), 'cycle17': ("{% cycle 'a' 'b' 'c' as abc silent %}{% cycle abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}", {}, ""),
'cycle18': ("{% cycle 'a' 'b' 'c' as foo invalid_flag %}", {}, template.TemplateSyntaxError), 'cycle18': ("{% cycle 'a' 'b' 'c' as foo invalid_flag %}", {}, template.TemplateSyntaxError),
'cycle19': ("{% cycle 'a' 'b' as silent %}{% cycle silent %}", {}, "ab"), 'cycle19': ("{% cycle 'a' 'b' as silent %}{% cycle silent %}", {}, "ab"),
'cycle20': ("{% cycle one two as foo %} &amp; {% cycle foo %}", {'one': 'A & B', 'two': 'C & D'}, "A & B &amp; C & D"), 'cycle20': ("{% cycle one two as foo %} &amp; {% cycle foo %}", {'one': 'A & B', 'two': 'C & D'}, "A &amp; B &amp; C &amp; D"),
'cycle21': ("{% filter force_escape %}{% cycle one two as foo %} & {% cycle foo %}{% endfilter %}", {'one': 'A & B', 'two': 'C & D'}, "A &amp; B &amp; C &amp; D"), 'cycle21': ("{% filter force_escape %}{% cycle one two as foo %} & {% cycle foo %}{% endfilter %}", {'one': 'A & B', 'two': 'C & D'}, "A &amp;amp; B &amp; C &amp;amp; D"),
'cycle22': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{{ x }}{% endfor %}", {'values': [1, 2, 3, 4]}, "1234"), 'cycle22': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{{ x }}{% endfor %}", {'values': [1, 2, 3, 4]}, "1234"),
'cycle23': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{{ abc }}{{ x }}{% endfor %}", {'values': [1, 2, 3, 4]}, "a1b2c3a4"), 'cycle23': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{{ abc }}{{ x }}{% endfor %}", {'values': [1, 2, 3, 4]}, "a1b2c3a4"),
'included-cycle': ('{{ abc }}', {'abc': 'xxx'}, 'xxx'), 'included-cycle': ('{{ abc }}', {'abc': 'xxx'}, 'xxx'),
'cycle24': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{% include 'included-cycle' %}{% endfor %}", {'values': [1, 2, 3, 4]}, "abca"), 'cycle24': ("{% for x in values %}{% cycle 'a' 'b' 'c' as abc silent %}{% include 'included-cycle' %}{% endfor %}", {'values': [1, 2, 3, 4]}, "abca"),
'cycle25': ('{% cycle a as abc %}', {'a': '<'}, '<'), 'cycle25': ('{% cycle a as abc %}', {'a': '<'}, '&lt;'),
'cycle26': ('{% load cycle from future %}{% cycle a b as ab %}{% cycle ab %}', {'a': '<', 'b': '>'}, '&lt;&gt;'), 'cycle26': ('{% load cycle from future %}{% cycle a b as ab %}{% cycle ab %}', {'a': '<', 'b': '>'}, '&lt;&gt;'),
'cycle27': ('{% load cycle from future %}{% autoescape off %}{% cycle a b as ab %}{% cycle ab %}{% endautoescape %}', {'a': '<', 'b': '>'}, '<>'), 'cycle27': ('{% load cycle from future %}{% autoescape off %}{% cycle a b as ab %}{% cycle ab %}{% endautoescape %}', {'a': '<', 'b': '>'}, '<>'),
@ -929,7 +929,7 @@ class TemplateTests(TestCase):
'firstof07': ('{% firstof a b "c" %}', {'a': 0}, 'c'), 'firstof07': ('{% firstof a b "c" %}', {'a': 0}, 'c'),
'firstof08': ('{% firstof a b "c and d" %}', {'a': 0, 'b': 0}, 'c and d'), 'firstof08': ('{% firstof a b "c and d" %}', {'a': 0, 'b': 0}, 'c and d'),
'firstof09': ('{% firstof %}', {}, template.TemplateSyntaxError), 'firstof09': ('{% firstof %}', {}, template.TemplateSyntaxError),
'firstof10': ('{% firstof a %}', {'a': '<'}, '<'), 'firstof10': ('{% firstof a %}', {'a': '<'}, '&lt;'),
'firstof11': ('{% load firstof from future %}{% firstof a b %}', {'a': '<', 'b': '>'}, '&lt;'), 'firstof11': ('{% load firstof from future %}{% firstof a b %}', {'a': '<', 'b': '>'}, '&lt;'),
'firstof12': ('{% load firstof from future %}{% firstof a b %}', {'a': '', 'b': '>'}, '&gt;'), 'firstof12': ('{% load firstof from future %}{% firstof a b %}', {'a': '', 'b': '>'}, '&gt;'),