Added 'format_html' utility for formatting HTML fragments safely
This commit is contained in:
parent
f33e150369
commit
bee498f3a2
|
@ -72,6 +72,37 @@ def conditional_escape(text):
|
||||||
else:
|
else:
|
||||||
return escape(text)
|
return escape(text)
|
||||||
|
|
||||||
|
def format_html(format_string, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Similar to str.format, but passes all arguments through conditional_escape,
|
||||||
|
and calls 'mark_safe' on the result. This function should be used instead
|
||||||
|
of str.format or % interpolation to build up small HTML fragments.
|
||||||
|
"""
|
||||||
|
args_safe = map(conditional_escape, args)
|
||||||
|
kwargs_safe = dict([(k, conditional_escape(v)) for (k, v) in
|
||||||
|
kwargs.iteritems()])
|
||||||
|
return mark_safe(format_string.format(*args_safe, **kwargs_safe))
|
||||||
|
|
||||||
|
def format_html_join(sep, format_string, args_generator):
|
||||||
|
"""
|
||||||
|
A wrapper format_html, for the common case of a group of arguments that need
|
||||||
|
to be formatted using the same format string, and then joined using
|
||||||
|
'sep'. 'sep' is also passed through conditional_escape.
|
||||||
|
|
||||||
|
'args_generator' should be an iterator that returns the sequence of 'args'
|
||||||
|
that will be passed to format_html.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
format_html_join('\n', "<li>{0} {1}</li>", ((u.first_name, u.last_name)
|
||||||
|
for u in users))
|
||||||
|
|
||||||
|
"""
|
||||||
|
return mark_safe(conditional_escape(sep).join(
|
||||||
|
format_html(format_string, *tuple(args))
|
||||||
|
for args in args_generator))
|
||||||
|
|
||||||
|
|
||||||
def linebreaks(value, autoescape=False):
|
def linebreaks(value, autoescape=False):
|
||||||
"""Converts newlines into <p> and <br />s."""
|
"""Converts newlines into <p> and <br />s."""
|
||||||
value = normalize_newlines(value)
|
value = normalize_newlines(value)
|
||||||
|
|
|
@ -410,6 +410,45 @@ escaping HTML.
|
||||||
Similar to ``escape()``, except that it doesn't operate on pre-escaped strings,
|
Similar to ``escape()``, except that it doesn't operate on pre-escaped strings,
|
||||||
so it will not double escape.
|
so it will not double escape.
|
||||||
|
|
||||||
|
.. function:: format_html(format_string, *args, **kwargs)
|
||||||
|
|
||||||
|
This is similar to `str.format`_, except that it is appropriate for
|
||||||
|
building up HTML fragments. All args and kwargs are passed through
|
||||||
|
:func:`conditional_escape` before being passed to ``str.format``.
|
||||||
|
|
||||||
|
For the case of building up small HTML fragments, this function is to be
|
||||||
|
preferred over string interpolation using ``%`` or ``str.format`` directly,
|
||||||
|
because it applies escaping to all arguments - just like the Template system
|
||||||
|
applies escaping by default.
|
||||||
|
|
||||||
|
So, instead of writing:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
mark_safe(u"%s <b>%s</b> %s" % (some_html,
|
||||||
|
escape(some_text),
|
||||||
|
escape(some_other_text),
|
||||||
|
))
|
||||||
|
|
||||||
|
you should instead use:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
format_html(u"%{0} <b>{1}</b> {2}",
|
||||||
|
mark_safe(some_html), some_text, some_other_text)
|
||||||
|
|
||||||
|
This has the advantage that you don't need to apply :func:`escape` to each
|
||||||
|
argument and risk a bug and an XSS vulnerability if you forget one.
|
||||||
|
|
||||||
|
Note that although this function uses ``str.format`` to do the
|
||||||
|
interpolation, some of the formatting options provided by `str.format`_
|
||||||
|
(e.g. number formatting) will not work, since all arguments are passed
|
||||||
|
through :func:`conditional_escape` which (ultimately) calls
|
||||||
|
:func:`~django.utils.encoding.force_unicode` on the values.
|
||||||
|
|
||||||
|
|
||||||
|
.. _str.format: http://docs.python.org/library/stdtypes.html#str.format
|
||||||
|
|
||||||
``django.utils.http``
|
``django.utils.http``
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,17 @@ class TestUtilsHtml(unittest.TestCase):
|
||||||
# Verify it doesn't double replace &.
|
# Verify it doesn't double replace &.
|
||||||
self.check_output(f, '<&', '<&')
|
self.check_output(f, '<&', '<&')
|
||||||
|
|
||||||
|
def test_format_html(self):
|
||||||
|
self.assertEqual(
|
||||||
|
html.format_html(u"{0} {1} {third} {fourth}",
|
||||||
|
u"< Dangerous >",
|
||||||
|
html.mark_safe(u"<b>safe</b>"),
|
||||||
|
third="< dangerous again",
|
||||||
|
fourth=html.mark_safe(u"<i>safe again</i>")
|
||||||
|
),
|
||||||
|
u"< Dangerous > <b>safe</b> < dangerous again <i>safe again</i>"
|
||||||
|
)
|
||||||
|
|
||||||
def test_linebreaks(self):
|
def test_linebreaks(self):
|
||||||
f = html.linebreaks
|
f = html.linebreaks
|
||||||
items = (
|
items = (
|
||||||
|
|
Loading…
Reference in New Issue