diff --git a/regulations/templatetags/macros.py b/regulations/templatetags/macros.py
index 2e149b884..c56ae1372 100644
--- a/regulations/templatetags/macros.py
+++ b/regulations/templatetags/macros.py
@@ -1,16 +1,19 @@
"""Macros used to make a few common tasks in templates easier"""
from django import template
+from django.utils.html import escape
register = template.Library()
@register.inclusion_tag('regulations/macros/external_link.html')
def external_link(url, text, classes="", title=""):
- return {"url": url, "text": text, "classes": classes, "title": title}
+ return {"url": escape(url), "text": escape(text),
+ "classes": escape(classes), "title": escape(title)}
@register.inclusion_tag('regulations/macros/search_for.html')
def search_for(terms, reg, version, text=None):
if text is None:
text = terms
- return {"terms": terms, "reg": reg, "version": version, "text": text}
+ return {"terms": terms, "reg": reg, "version": version,
+ "text": escape(text)}
diff --git a/regulations/tests/templatetags_macros_tests.py b/regulations/tests/templatetags_macros_tests.py
index 69f028625..9e283684e 100644
--- a/regulations/tests/templatetags_macros_tests.py
+++ b/regulations/tests/templatetags_macros_tests.py
@@ -1,43 +1,53 @@
-from unittest import TestCase
from xml.etree import ElementTree as etree # nosec - see usage below
from django.template import Context, Template
-class MacrosTests(TestCase):
- def _gen_link(self, content):
- """Shorthand for passing the content into a template and rendering"""
- text = "{% load macros %}" + content
- as_str = Template(text).render(Context({}))
- # Safe because: we've constructed the XML
- as_xml = etree.fromstring("{}".format(as_str)) # nosec
- anchors = as_xml.findall('.//a')
- self.assertTrue(len(anchors) > 0)
- return anchors[0]
-
- def test_external_link_no_optional(self):
- """The classes and title fields are optional. We should generate an
- appropriate link"""
- anchor = self._gen_link(
- '{% external_link url="http://example.com/path" text="Click" %}')
- self.assertEqual(anchor.get('target'), '_blank')
- self.assertEqual(anchor.get('href'), 'http://example.com/path')
- self.assertFalse('title' in anchor.attrib)
- self.assertTrue('aria-label' in anchor.attrib)
- self.assertTrue('Click' in anchor.text)
-
- def test_external_link_classes_title(self):
- """The classes and title fields _can_ be added"""
- anchor = self._gen_link(
- '{% external_link url="url" text="text" classes="some here" '
- 'title="My Title" %}')
- self.assertEqual(anchor.get('title'), 'My Title')
- self.assertTrue('some here' in anchor.get('class'))
-
- def test_search_for(self):
- """Macro should url-encode search terms."""
- anchor = self._gen_link(
- '{% search_for terms="has spaces" reg="1234" version="vvv" %}')
- self.assertTrue('1234' in anchor.get('href'))
- self.assertTrue('vvv' in anchor.get('href'))
- self.assertTrue('has%20spaces' in anchor.get('href'))
+def _gen_link(content):
+ """Shorthand for passing the content into a template and rendering"""
+ text = "{% load macros %}" + content
+ as_str = Template(text).render(Context({}))
+ # Safe because: we've constructed the XML
+ as_xml = etree.fromstring("{}".format(as_str)) # nosec
+ anchors = as_xml.findall('.//a')
+ assert len(anchors) > 0
+ return anchors[0]
+
+
+def test_external_link_no_optional():
+ """The classes and title fields are optional. We should generate an
+ appropriate link"""
+ anchor = _gen_link(
+ '{% external_link url="http://example.com/path" text="Click" %}')
+ assert anchor.get('target') == '_blank'
+ assert anchor.get('href') == 'http://example.com/path'
+ assert 'title' not in anchor.attrib
+ assert 'aria-label' in anchor.attrib
+ assert 'Click' in anchor.text
+
+
+def test_external_link_classes_title():
+ """The classes and title fields _can_ be added"""
+ anchor = _gen_link(
+ '{% external_link url="url" text="text" classes="some here" '
+ 'title="My Title" %}')
+ assert anchor.get('title') == 'My Title'
+ assert 'some here' in anchor.get('class')
+
+
+def test_external_link_is_escaped():
+ # LXML decodes the value for us, so let's look at the raw markup
+ text = ('{% load macros %}'
+ '{% external_link url="http://example.com/?param=1&other=value" '
+ 'text="value" %}')
+ as_str = Template(text).render(Context({}))
+ assert 'http://example.com/?param=1&other=value' in as_str
+
+
+def test_search_for():
+ """Macro should url-encode search terms."""
+ anchor = _gen_link(
+ '{% search_for terms="has spaces" reg="1234" version="vvv" %}')
+ assert '1234' in anchor.get('href')
+ assert 'vvv' in anchor.get('href')
+ assert 'has%20spaces' in anchor.get('href')