Commit bee498f3 authored by Luke Plant's avatar Luke Plant
Browse files

Added 'format_html' utility for formatting HTML fragments safely

parent f33e1503
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
@@ -72,6 +72,37 @@ def conditional_escape(text):
    else:
        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):
    """Converts newlines into <p> and <br />s."""
    value = normalize_newlines(value)
+39 −0
Original line number Diff line number Diff line
@@ -410,6 +410,45 @@ escaping HTML.
    Similar to ``escape()``, except that it doesn't operate on pre-escaped strings,
    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``
=====================

+11 −0
Original line number Diff line number Diff line
@@ -34,6 +34,17 @@ class TestUtilsHtml(unittest.TestCase):
        # Verify it doesn't double replace &.
        self.check_output(f, '<&', '&lt;&amp;')

    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"&lt; Dangerous &gt; <b>safe</b> &lt; dangerous again <i>safe again</i>"
            )

    def test_linebreaks(self):
        f = html.linebreaks
        items = (