123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- # -*- coding: utf-8 -*-
- import pytest
- from jinja2 import Environment
- from jinja2 import escape
- from jinja2 import Markup
- from jinja2._compat import text_type
- from jinja2.exceptions import SecurityError
- from jinja2.exceptions import TemplateRuntimeError
- from jinja2.exceptions import TemplateSyntaxError
- from jinja2.nodes import EvalContext
- from jinja2.sandbox import ImmutableSandboxedEnvironment
- from jinja2.sandbox import SandboxedEnvironment
- from jinja2.sandbox import unsafe
- class PrivateStuff(object):
- def bar(self):
- return 23
- @unsafe
- def foo(self):
- return 42
- def __repr__(self):
- return "PrivateStuff"
- class PublicStuff(object):
- def bar(self):
- return 23
- def _foo(self):
- return 42
- def __repr__(self):
- return "PublicStuff"
- class TestSandbox(object):
- def test_unsafe(self, env):
- env = SandboxedEnvironment()
- pytest.raises(
- SecurityError, env.from_string("{{ foo.foo() }}").render, foo=PrivateStuff()
- )
- assert env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()) == "23"
- pytest.raises(
- SecurityError, env.from_string("{{ foo._foo() }}").render, foo=PublicStuff()
- )
- assert env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()) == "23"
- assert env.from_string("{{ foo.__class__ }}").render(foo=42) == ""
- assert env.from_string("{{ foo.func_code }}").render(foo=lambda: None) == ""
- # security error comes from __class__ already.
- pytest.raises(
- SecurityError,
- env.from_string("{{ foo.__class__.__subclasses__() }}").render,
- foo=42,
- )
- def test_immutable_environment(self, env):
- env = ImmutableSandboxedEnvironment()
- pytest.raises(SecurityError, env.from_string("{{ [].append(23) }}").render)
- pytest.raises(SecurityError, env.from_string("{{ {1:2}.clear() }}").render)
- def test_restricted(self, env):
- env = SandboxedEnvironment()
- pytest.raises(
- TemplateSyntaxError,
- env.from_string,
- "{% for item.attribute in seq %}...{% endfor %}",
- )
- pytest.raises(
- TemplateSyntaxError,
- env.from_string,
- "{% for foo, bar.baz in seq %}...{% endfor %}",
- )
- def test_markup_operations(self, env):
- # adding two strings should escape the unsafe one
- unsafe = '<script type="application/x-some-script">alert("foo");</script>'
- safe = Markup("<em>username</em>")
- assert unsafe + safe == text_type(escape(unsafe)) + text_type(safe)
- # string interpolations are safe to use too
- assert Markup("<em>%s</em>") % "<bad user>" == "<em><bad user></em>"
- assert (
- Markup("<em>%(username)s</em>") % {"username": "<bad user>"}
- == "<em><bad user></em>"
- )
- # an escaped object is markup too
- assert type(Markup("foo") + "bar") is Markup
- # and it implements __html__ by returning itself
- x = Markup("foo")
- assert x.__html__() is x
- # it also knows how to treat __html__ objects
- class Foo(object):
- def __html__(self):
- return "<em>awesome</em>"
- def __unicode__(self):
- return "awesome"
- assert Markup(Foo()) == "<em>awesome</em>"
- assert (
- Markup("<strong>%s</strong>") % Foo() == "<strong><em>awesome</em></strong>"
- )
- # escaping and unescaping
- assert escape("\"<>&'") == ""<>&'"
- assert Markup("<em>Foo & Bar</em>").striptags() == "Foo & Bar"
- assert Markup("<test>").unescape() == "<test>"
- def test_template_data(self, env):
- env = Environment(autoescape=True)
- t = env.from_string(
- "{% macro say_hello(name) %}"
- "<p>Hello {{ name }}!</p>{% endmacro %}"
- '{{ say_hello("<blink>foo</blink>") }}'
- )
- escaped_out = "<p>Hello <blink>foo</blink>!</p>"
- assert t.render() == escaped_out
- assert text_type(t.module) == escaped_out
- assert escape(t.module) == escaped_out
- assert t.module.say_hello("<blink>foo</blink>") == escaped_out
- assert (
- escape(t.module.say_hello(EvalContext(env), "<blink>foo</blink>"))
- == escaped_out
- )
- assert escape(t.module.say_hello("<blink>foo</blink>")) == escaped_out
- def test_attr_filter(self, env):
- env = SandboxedEnvironment()
- tmpl = env.from_string('{{ cls|attr("__subclasses__")() }}')
- pytest.raises(SecurityError, tmpl.render, cls=int)
- def test_binary_operator_intercepting(self, env):
- def disable_op(left, right):
- raise TemplateRuntimeError("that operator so does not work")
- for expr, ctx, rv in ("1 + 2", {}, "3"), ("a + 2", {"a": 2}, "4"):
- env = SandboxedEnvironment()
- env.binop_table["+"] = disable_op
- t = env.from_string("{{ %s }}" % expr)
- assert t.render(ctx) == rv
- env.intercepted_binops = frozenset(["+"])
- t = env.from_string("{{ %s }}" % expr)
- with pytest.raises(TemplateRuntimeError):
- t.render(ctx)
- def test_unary_operator_intercepting(self, env):
- def disable_op(arg):
- raise TemplateRuntimeError("that operator so does not work")
- for expr, ctx, rv in ("-1", {}, "-1"), ("-a", {"a": 2}, "-2"):
- env = SandboxedEnvironment()
- env.unop_table["-"] = disable_op
- t = env.from_string("{{ %s }}" % expr)
- assert t.render(ctx) == rv
- env.intercepted_unops = frozenset(["-"])
- t = env.from_string("{{ %s }}" % expr)
- with pytest.raises(TemplateRuntimeError):
- t.render(ctx)
- class TestStringFormat(object):
- def test_basic_format_safety(self):
- env = SandboxedEnvironment()
- t = env.from_string('{{ "a{0.__class__}b".format(42) }}')
- assert t.render() == "ab"
- def test_basic_format_all_okay(self):
- env = SandboxedEnvironment()
- t = env.from_string('{{ "a{0.foo}b".format({"foo": 42}) }}')
- assert t.render() == "a42b"
- def test_safe_format_safety(self):
- env = SandboxedEnvironment()
- t = env.from_string('{{ ("a{0.__class__}b{1}"|safe).format(42, "<foo>") }}')
- assert t.render() == "ab<foo>"
- def test_safe_format_all_okay(self):
- env = SandboxedEnvironment()
- t = env.from_string('{{ ("a{0.foo}b{1}"|safe).format({"foo": 42}, "<foo>") }}')
- assert t.render() == "a42b<foo>"
- @pytest.mark.skipif(
- not hasattr(str, "format_map"), reason="requires str.format_map method"
- )
- class TestStringFormatMap(object):
- def test_basic_format_safety(self):
- env = SandboxedEnvironment()
- t = env.from_string('{{ "a{x.__class__}b".format_map({"x":42}) }}')
- assert t.render() == "ab"
- def test_basic_format_all_okay(self):
- env = SandboxedEnvironment()
- t = env.from_string('{{ "a{x.foo}b".format_map({"x":{"foo": 42}}) }}')
- assert t.render() == "a42b"
- def test_safe_format_all_okay(self):
- env = SandboxedEnvironment()
- t = env.from_string(
- '{{ ("a{x.foo}b{y}"|safe).format_map({"x":{"foo": 42}, "y":"<foo>"}) }}'
- )
- assert t.render() == "a42b<foo>"
|