Loading django/template/defaulttags.py +33 −15 Original line number Diff line number Diff line Loading @@ -5,13 +5,15 @@ import sys import re from datetime import datetime from itertools import groupby, cycle as itertools_cycle import warnings from django.conf import settings from django.template.base import (Node, NodeList, Template, Context, Library, TemplateSyntaxError, VariableDoesNotExist, InvalidTemplateLibrary, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END, VARIABLE_ATTRIBUTE_SEPARATOR, get_library, token_kwargs, kwarg_re) VARIABLE_ATTRIBUTE_SEPARATOR, get_library, token_kwargs, kwarg_re, _render_value_in_context) from django.template.smartif import IfParser, Literal from django.template.defaultfilters import date from django.utils.encoding import smart_text Loading Loading @@ -54,15 +56,15 @@ class CsrfTokenNode(Node): # misconfiguration, so we raise a warning from django.conf import settings if settings.DEBUG: import warnings warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.") return '' class CycleNode(Node): def __init__(self, cyclevars, variable_name=None, silent=False): def __init__(self, cyclevars, variable_name=None, silent=False, escape=False): self.cyclevars = cyclevars self.variable_name = variable_name self.silent = silent self.escape = escape # only while the "future" version exists def render(self, context): if self not in context.render_context: Loading @@ -74,7 +76,9 @@ class CycleNode(Node): context[self.variable_name] = value if self.silent: return '' return value if not self.escape: value = mark_safe(value) return _render_value_in_context(value, context) class DebugNode(Node): def render(self, context): Loading @@ -97,14 +101,17 @@ class FilterNode(Node): return filtered class FirstOfNode(Node): def __init__(self, vars): self.vars = vars def __init__(self, variables, escape=False): self.vars = variables self.escape = escape # only while the "future" version exists def render(self, context): for var in self.vars: value = var.resolve(context, True) if value: return smart_text(value) if not self.escape: value = mark_safe(value) return _render_value_in_context(value, context) return '' class ForNode(Node): Loading Loading @@ -508,7 +515,7 @@ def comment(parser, token): return CommentNode() @register.tag def cycle(parser, token): def cycle(parser, token, escape=False): """ Cycles among the given strings each time this tag is encountered. Loading Loading @@ -541,6 +548,11 @@ def cycle(parser, token): {% endfor %} """ if not escape: warnings.warn( "'The syntax for the `cycle` template tag is changing. Load it " "from the `future` tag library to start using the new behavior.", PendingDeprecationWarning, stacklevel=2) # 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 Loading Loading @@ -588,13 +600,13 @@ def cycle(parser, token): if as_form: name = args[-1] values = [parser.compile_filter(arg) for arg in args[1:-2]] node = CycleNode(values, name, silent=silent) node = CycleNode(values, name, silent=silent, escape=escape) if not hasattr(parser, '_namedCycleNodes'): parser._namedCycleNodes = {} parser._namedCycleNodes[name] = node else: values = [parser.compile_filter(arg) for arg in args[1:]] node = CycleNode(values) node = CycleNode(values, escape=escape) return node @register.tag Loading Loading @@ -643,7 +655,7 @@ def do_filter(parser, token): return FilterNode(filter_expr, nodelist) @register.tag def firstof(parser, token): def firstof(parser, token, escape=False): """ Outputs the first variable passed that is not False, without escaping. Loading @@ -657,11 +669,11 @@ def firstof(parser, token): {% if var1 %} {{ var1|safe }} {% else %}{% if var2 %} {% elif var2 %} {{ var2|safe }} {% else %}{% if var3 %} {% elif var3 %} {{ var3|safe }} {% endif %}{% endif %}{% endif %} {% endif %} but obviously much cleaner! Loading @@ -677,10 +689,16 @@ def firstof(parser, token): {% endfilter %} """ if not escape: warnings.warn( "'The syntax for the `firstof` template tag is changing. Load it " "from the `future` tag library to start using the new behavior.", PendingDeprecationWarning, stacklevel=2) bits = token.split_contents()[1:] if len(bits) < 1: raise TemplateSyntaxError("'firstof' statement requires at least one argument") return FirstOfNode([parser.compile_filter(bit) for bit in bits]) return FirstOfNode([parser.compile_filter(bit) for bit in bits], escape=escape) @register.tag('for') def do_for(parser, token): Loading django/templatetags/future.py +54 −3 Original line number Diff line number Diff line from django.template import Library from django.template.defaulttags import url as default_url, ssi as default_ssi from django.template import defaulttags register = Library() @register.tag def ssi(parser, token): # Used for deprecation path during 1.3/1.4, will be removed in 2.0 return default_ssi(parser, token) return defaulttags.ssi(parser, token) @register.tag def url(parser, token): # Used for deprecation path during 1.3/1.4, will be removed in 2.0 return default_url(parser, token) return defaulttags.url(parser, token) @register.tag def cycle(parser, token): """ This is the future version of `cycle` with auto-escaping. By default all strings are escaped. If you want to disable auto-escaping of variables you can use: {% autoescape off %} {% cycle var1 var2 var3 as somecycle %} {% autoescape %} Or if only some variables should be escaped, you can use: {% cycle var1 var2|safe var3|safe as somecycle %} """ return defaulttags.cycle(parser, token, escape=True) @register.tag def firstof(parser, token): """ This is the future version of `firstof` with auto-escaping. This is equivalent to: {% if var1 %} {{ var1 }} {% elif var2 %} {{ var2 }} {% elif var3 %} {{ var3 }} {% endif %} If you want to disable auto-escaping of variables you can use: {% autoescape off %} {% firstof var1 var2 var3 "<strong>fallback value</strong>" %} {% autoescape %} Or if only some variables should be escaped, you can use: {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %} """ return defaulttags.firstof(parser, token, escape=True) docs/internals/deprecation.txt +4 −0 Original line number Diff line number Diff line Loading @@ -323,6 +323,10 @@ these changes. 1.8 --- * The :ttag:`cycle` and :ttag:`firstof` template tags will auto-escape their arguments. In 1.6 and 1.7, this behavior is provided by the version of these tags in the ``future`` template tag library. * The ``SEND_BROKEN_LINK_EMAILS`` setting will be removed. Add the :class:`django.middleware.common.BrokenLinkEmailsMiddleware` middleware to your :setting:`MIDDLEWARE_CLASSES` setting instead. Loading docs/ref/templates/builtins.txt +43 −12 Original line number Diff line number Diff line Loading @@ -147,9 +147,8 @@ You can use any number of values in a ``{% cycle %}`` tag, separated by spaces. Values enclosed in single (``'``) or double quotes (``"``) are treated as string literals, while values without quotes are treated as template variables. Note that the variables included in the cycle will not be escaped. This is because template tags do not escape their content. Any HTML or Javascript code contained in the printed variable will be rendered Note that currently the variables included in the cycle will not be escaped. Any HTML or Javascript code contained in the printed variable will be rendered as-is, which could potentially lead to security issues. For backwards compatibility, the ``{% cycle %}`` tag supports the much inferior Loading Loading @@ -190,6 +189,22 @@ call to ``{% cycle %}`` doesn't specify silent:: {% cycle 'row1' 'row2' as rowcolors silent %} {% cycle rowcolors %} .. 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 debug Loading Loading @@ -257,28 +272,44 @@ This is equivalent to:: {% if var1 %} {{ var1|safe }} {% else %}{% if var2 %} {% elif var2 %} {{ var2|safe }} {% else %}{% if var3 %} {% elif var3 %} {{ var3|safe }} {% endif %}{% endif %}{% endif %} {% endif %} You can also use a literal string as a fallback value in case all passed variables are False:: {% firstof var1 var2 var3 "fallback value" %} Note that the variables included in the firstof tag will not be escaped. This is because template tags do not escape their content. 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:: Note that currently the variables included in the firstof tag will not be 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 %} {% firstof var1 var2 var3 "fallback value" %} {% endfilter %} .. versionchanged:: 1.6 To improve safety, future versions of ``firstof`` will automatically escape 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 for Loading docs/releases/1.6.txt +28 −0 Original line number Diff line number Diff line Loading @@ -160,6 +160,34 @@ Backwards incompatible changes in 1.6 Features deprecated in 1.6 ========================== Changes to :ttag:`cycle` and :ttag:`firstof` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The template system generally escapes all variables to avoid XSS attacks. However, due to an accident of history, the :ttag:`cycle` and :ttag:`firstof` tags render their arguments as-is. Django 1.6 starts a process to correct this inconsistency. The ``future`` template library provides alternate implementations of :ttag:`cycle` and :ttag:`firstof` that autoescape their inputs. If you're using these tags, you're encourage to include the following line at the top of your templates to enable the new behavior:: {% load cycle from future %} or:: {% load firstof from future %} The tags implementing the old behavior have been deprecated, and in Django 1.8, the old behavior will be replaced with the new behavior. To ensure compatibility with future versions of Django, existing templates should be modified to use the ``future`` versions. If necessary, you can temporarily disable auto-escaping with :func:`~django.utils.safestring.mark_safe` or :ttag:`{% autoescape off %} <autoescape>`. ``SEND_BROKEN_LINK_EMAILS`` setting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Loading Loading
django/template/defaulttags.py +33 −15 Original line number Diff line number Diff line Loading @@ -5,13 +5,15 @@ import sys import re from datetime import datetime from itertools import groupby, cycle as itertools_cycle import warnings from django.conf import settings from django.template.base import (Node, NodeList, Template, Context, Library, TemplateSyntaxError, VariableDoesNotExist, InvalidTemplateLibrary, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END, VARIABLE_ATTRIBUTE_SEPARATOR, get_library, token_kwargs, kwarg_re) VARIABLE_ATTRIBUTE_SEPARATOR, get_library, token_kwargs, kwarg_re, _render_value_in_context) from django.template.smartif import IfParser, Literal from django.template.defaultfilters import date from django.utils.encoding import smart_text Loading Loading @@ -54,15 +56,15 @@ class CsrfTokenNode(Node): # misconfiguration, so we raise a warning from django.conf import settings if settings.DEBUG: import warnings warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.") return '' class CycleNode(Node): def __init__(self, cyclevars, variable_name=None, silent=False): def __init__(self, cyclevars, variable_name=None, silent=False, escape=False): self.cyclevars = cyclevars self.variable_name = variable_name self.silent = silent self.escape = escape # only while the "future" version exists def render(self, context): if self not in context.render_context: Loading @@ -74,7 +76,9 @@ class CycleNode(Node): context[self.variable_name] = value if self.silent: return '' return value if not self.escape: value = mark_safe(value) return _render_value_in_context(value, context) class DebugNode(Node): def render(self, context): Loading @@ -97,14 +101,17 @@ class FilterNode(Node): return filtered class FirstOfNode(Node): def __init__(self, vars): self.vars = vars def __init__(self, variables, escape=False): self.vars = variables self.escape = escape # only while the "future" version exists def render(self, context): for var in self.vars: value = var.resolve(context, True) if value: return smart_text(value) if not self.escape: value = mark_safe(value) return _render_value_in_context(value, context) return '' class ForNode(Node): Loading Loading @@ -508,7 +515,7 @@ def comment(parser, token): return CommentNode() @register.tag def cycle(parser, token): def cycle(parser, token, escape=False): """ Cycles among the given strings each time this tag is encountered. Loading Loading @@ -541,6 +548,11 @@ def cycle(parser, token): {% endfor %} """ if not escape: warnings.warn( "'The syntax for the `cycle` template tag is changing. Load it " "from the `future` tag library to start using the new behavior.", PendingDeprecationWarning, stacklevel=2) # 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 Loading Loading @@ -588,13 +600,13 @@ def cycle(parser, token): if as_form: name = args[-1] values = [parser.compile_filter(arg) for arg in args[1:-2]] node = CycleNode(values, name, silent=silent) node = CycleNode(values, name, silent=silent, escape=escape) if not hasattr(parser, '_namedCycleNodes'): parser._namedCycleNodes = {} parser._namedCycleNodes[name] = node else: values = [parser.compile_filter(arg) for arg in args[1:]] node = CycleNode(values) node = CycleNode(values, escape=escape) return node @register.tag Loading Loading @@ -643,7 +655,7 @@ def do_filter(parser, token): return FilterNode(filter_expr, nodelist) @register.tag def firstof(parser, token): def firstof(parser, token, escape=False): """ Outputs the first variable passed that is not False, without escaping. Loading @@ -657,11 +669,11 @@ def firstof(parser, token): {% if var1 %} {{ var1|safe }} {% else %}{% if var2 %} {% elif var2 %} {{ var2|safe }} {% else %}{% if var3 %} {% elif var3 %} {{ var3|safe }} {% endif %}{% endif %}{% endif %} {% endif %} but obviously much cleaner! Loading @@ -677,10 +689,16 @@ def firstof(parser, token): {% endfilter %} """ if not escape: warnings.warn( "'The syntax for the `firstof` template tag is changing. Load it " "from the `future` tag library to start using the new behavior.", PendingDeprecationWarning, stacklevel=2) bits = token.split_contents()[1:] if len(bits) < 1: raise TemplateSyntaxError("'firstof' statement requires at least one argument") return FirstOfNode([parser.compile_filter(bit) for bit in bits]) return FirstOfNode([parser.compile_filter(bit) for bit in bits], escape=escape) @register.tag('for') def do_for(parser, token): Loading
django/templatetags/future.py +54 −3 Original line number Diff line number Diff line from django.template import Library from django.template.defaulttags import url as default_url, ssi as default_ssi from django.template import defaulttags register = Library() @register.tag def ssi(parser, token): # Used for deprecation path during 1.3/1.4, will be removed in 2.0 return default_ssi(parser, token) return defaulttags.ssi(parser, token) @register.tag def url(parser, token): # Used for deprecation path during 1.3/1.4, will be removed in 2.0 return default_url(parser, token) return defaulttags.url(parser, token) @register.tag def cycle(parser, token): """ This is the future version of `cycle` with auto-escaping. By default all strings are escaped. If you want to disable auto-escaping of variables you can use: {% autoescape off %} {% cycle var1 var2 var3 as somecycle %} {% autoescape %} Or if only some variables should be escaped, you can use: {% cycle var1 var2|safe var3|safe as somecycle %} """ return defaulttags.cycle(parser, token, escape=True) @register.tag def firstof(parser, token): """ This is the future version of `firstof` with auto-escaping. This is equivalent to: {% if var1 %} {{ var1 }} {% elif var2 %} {{ var2 }} {% elif var3 %} {{ var3 }} {% endif %} If you want to disable auto-escaping of variables you can use: {% autoescape off %} {% firstof var1 var2 var3 "<strong>fallback value</strong>" %} {% autoescape %} Or if only some variables should be escaped, you can use: {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %} """ return defaulttags.firstof(parser, token, escape=True)
docs/internals/deprecation.txt +4 −0 Original line number Diff line number Diff line Loading @@ -323,6 +323,10 @@ these changes. 1.8 --- * The :ttag:`cycle` and :ttag:`firstof` template tags will auto-escape their arguments. In 1.6 and 1.7, this behavior is provided by the version of these tags in the ``future`` template tag library. * The ``SEND_BROKEN_LINK_EMAILS`` setting will be removed. Add the :class:`django.middleware.common.BrokenLinkEmailsMiddleware` middleware to your :setting:`MIDDLEWARE_CLASSES` setting instead. Loading
docs/ref/templates/builtins.txt +43 −12 Original line number Diff line number Diff line Loading @@ -147,9 +147,8 @@ You can use any number of values in a ``{% cycle %}`` tag, separated by spaces. Values enclosed in single (``'``) or double quotes (``"``) are treated as string literals, while values without quotes are treated as template variables. Note that the variables included in the cycle will not be escaped. This is because template tags do not escape their content. Any HTML or Javascript code contained in the printed variable will be rendered Note that currently the variables included in the cycle will not be escaped. Any HTML or Javascript code contained in the printed variable will be rendered as-is, which could potentially lead to security issues. For backwards compatibility, the ``{% cycle %}`` tag supports the much inferior Loading Loading @@ -190,6 +189,22 @@ call to ``{% cycle %}`` doesn't specify silent:: {% cycle 'row1' 'row2' as rowcolors silent %} {% cycle rowcolors %} .. 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 debug Loading Loading @@ -257,28 +272,44 @@ This is equivalent to:: {% if var1 %} {{ var1|safe }} {% else %}{% if var2 %} {% elif var2 %} {{ var2|safe }} {% else %}{% if var3 %} {% elif var3 %} {{ var3|safe }} {% endif %}{% endif %}{% endif %} {% endif %} You can also use a literal string as a fallback value in case all passed variables are False:: {% firstof var1 var2 var3 "fallback value" %} Note that the variables included in the firstof tag will not be escaped. This is because template tags do not escape their content. 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:: Note that currently the variables included in the firstof tag will not be 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 %} {% firstof var1 var2 var3 "fallback value" %} {% endfilter %} .. versionchanged:: 1.6 To improve safety, future versions of ``firstof`` will automatically escape 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 for Loading
docs/releases/1.6.txt +28 −0 Original line number Diff line number Diff line Loading @@ -160,6 +160,34 @@ Backwards incompatible changes in 1.6 Features deprecated in 1.6 ========================== Changes to :ttag:`cycle` and :ttag:`firstof` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The template system generally escapes all variables to avoid XSS attacks. However, due to an accident of history, the :ttag:`cycle` and :ttag:`firstof` tags render their arguments as-is. Django 1.6 starts a process to correct this inconsistency. The ``future`` template library provides alternate implementations of :ttag:`cycle` and :ttag:`firstof` that autoescape their inputs. If you're using these tags, you're encourage to include the following line at the top of your templates to enable the new behavior:: {% load cycle from future %} or:: {% load firstof from future %} The tags implementing the old behavior have been deprecated, and in Django 1.8, the old behavior will be replaced with the new behavior. To ensure compatibility with future versions of Django, existing templates should be modified to use the ``future`` versions. If necessary, you can temporarily disable auto-escaping with :func:`~django.utils.safestring.mark_safe` or :ttag:`{% autoescape off %} <autoescape>`. ``SEND_BROKEN_LINK_EMAILS`` setting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Loading