Skip to content

Commit 2613fbb

Browse files
dnlzrgzfsbraun
andauthored
fix: prevent context pollution (#213)
* fix: prevent context pollution * refactor: improve context encapsulation Use single context.push() and add comments explaining the context stack length assertion. * test: improve context pollution test case * Apply suggestion from @fsbraun --------- Co-authored-by: Fabian Braun <fsbraun@gmx.de>
1 parent 97a2270 commit 2613fbb

File tree

2 files changed

+50
-25
lines changed

2 files changed

+50
-25
lines changed

src/djangocms_snippet/templatetags/snippet_tags.py

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -82,31 +82,33 @@ def get_content_render(self, context, instance):
8282
"""
8383
Render the snippet HTML, using a template if defined in its instance
8484
"""
85-
context.update(
86-
{
87-
"object": instance,
88-
}
89-
)
90-
try:
91-
if instance.template:
92-
context.update({"html": mark_safe(instance.html)})
93-
content = template.loader.render_to_string(
94-
instance.template,
95-
context.flatten(),
96-
)
97-
else:
98-
t = template.Template(instance.html)
99-
content = t.render(context)
100-
except template.TemplateDoesNotExist:
101-
content = _("Template %(template)s does not exist.") % {"template": instance.template}
102-
except Exception as e: # pragma: no cover
103-
content = escape(str(e))
104-
if self.parse_until:
105-
# In case we are running 'exceptionless'
106-
# Re-raise exception in order not to get the
107-
# error rendered
108-
raise
109-
return content
85+
86+
# Create the data to push to the context all at once, including
87+
# the 'html' if there's a template.
88+
context_data = {"object": instance}
89+
if instance.template:
90+
context_data["html"] = mark_safe(instance.html)
91+
92+
with context.push(**context_data):
93+
try:
94+
if instance.template:
95+
content = template.loader.render_to_string(
96+
instance.template,
97+
context.flatten(),
98+
)
99+
else:
100+
t = template.Template(instance.html)
101+
content = t.render(context)
102+
except template.TemplateDoesNotExist:
103+
content = _("Template %(template)s does not exist.") % {"template": instance.template}
104+
except Exception as e: # pragma: no cover
105+
content = escape(str(e))
106+
if self.parse_until:
107+
# In case we are running 'exceptionless'
108+
# Re-raise exception in order not to get the
109+
# error rendered
110+
raise
111+
return content
110112

111113

112114
@register.tag(name="snippet_fragment")

tests/test_templatetags.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,26 @@ def test_template_errors(self):
9090
with self.assertRaises(TemplateSyntaxError):
9191
# You need to specify at least a "snippet" ID, slug or instance
9292
template_to_render = Template("{% load snippet_tags %}{% snippet_fragment %}")
93+
94+
def test_context_pollution(self):
95+
snippet = SnippetWithVersionFactory(
96+
name="test snippet",
97+
html="<p>Snippet content</p>",
98+
slug="pollution_test",
99+
)
100+
snippet.versions.last().publish(user=self.get_superuser())
101+
102+
# Initialize context and capture the initial stack size. Since
103+
# the Context is just a stack of dicts, by checking the length of
104+
# the stack we ensure that every 'push' has a corresponding 'pop'.
105+
og_object = "This should not change"
106+
context = Context({"object": og_object})
107+
initial_stack_len = len(context.dicts)
108+
109+
template_to_render = Template('{% load snippet_tags %}{% snippet_fragment "pollution_test" %} "{{ object }}"')
110+
rendered_template = template_to_render.render(context)
111+
112+
self.assertIn(og_object, rendered_template)
113+
self.assertIn("<p>Snippet content</p>", rendered_template)
114+
self.assertEqual(context.get("object"), og_object)
115+
self.assertEqual(len(context.dicts), initial_stack_len, "Context stack leaked")

0 commit comments

Comments
 (0)