1import pytest
2
3from jinja2 import Environment
4from jinja2 import escape
5from jinja2.exceptions import SecurityError
6from jinja2.exceptions import TemplateRuntimeError
7from jinja2.exceptions import TemplateSyntaxError
8from jinja2.nodes import EvalContext
9from jinja2.sandbox import ImmutableSandboxedEnvironment
10from jinja2.sandbox import SandboxedEnvironment
11from jinja2.sandbox import unsafe
12
13
14class PrivateStuff:
15    def bar(self):
16        return 23
17
18    @unsafe
19    def foo(self):
20        return 42
21
22    def __repr__(self):
23        return "PrivateStuff"
24
25
26class PublicStuff:
27    def bar(self):
28        return 23
29
30    def _foo(self):
31        return 42
32
33    def __repr__(self):
34        return "PublicStuff"
35
36
37class TestSandbox:
38    def test_unsafe(self, env):
39        env = SandboxedEnvironment()
40        pytest.raises(
41            SecurityError, env.from_string("{{ foo.foo() }}").render, foo=PrivateStuff()
42        )
43        assert env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()) == "23"
44
45        pytest.raises(
46            SecurityError, env.from_string("{{ foo._foo() }}").render, foo=PublicStuff()
47        )
48        assert env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()) == "23"
49        assert env.from_string("{{ foo.__class__ }}").render(foo=42) == ""
50        assert env.from_string("{{ foo.func_code }}").render(foo=lambda: None) == ""
51        # security error comes from __class__ already.
52        pytest.raises(
53            SecurityError,
54            env.from_string("{{ foo.__class__.__subclasses__() }}").render,
55            foo=42,
56        )
57
58    def test_immutable_environment(self, env):
59        env = ImmutableSandboxedEnvironment()
60        pytest.raises(SecurityError, env.from_string("{{ [].append(23) }}").render)
61        pytest.raises(SecurityError, env.from_string("{{ {1:2}.clear() }}").render)
62
63    def test_restricted(self, env):
64        env = SandboxedEnvironment()
65        pytest.raises(
66            TemplateSyntaxError,
67            env.from_string,
68            "{% for item.attribute in seq %}...{% endfor %}",
69        )
70        pytest.raises(
71            TemplateSyntaxError,
72            env.from_string,
73            "{% for foo, bar.baz in seq %}...{% endfor %}",
74        )
75
76    def test_template_data(self, env):
77        env = Environment(autoescape=True)
78        t = env.from_string(
79            "{% macro say_hello(name) %}"
80            "<p>Hello {{ name }}!</p>{% endmacro %}"
81            '{{ say_hello("<blink>foo</blink>") }}'
82        )
83        escaped_out = "<p>Hello &lt;blink&gt;foo&lt;/blink&gt;!</p>"
84        assert t.render() == escaped_out
85        assert str(t.module) == escaped_out
86        assert escape(t.module) == escaped_out
87        assert t.module.say_hello("<blink>foo</blink>") == escaped_out
88        assert (
89            escape(t.module.say_hello(EvalContext(env), "<blink>foo</blink>"))
90            == escaped_out
91        )
92        assert escape(t.module.say_hello("<blink>foo</blink>")) == escaped_out
93
94    def test_attr_filter(self, env):
95        env = SandboxedEnvironment()
96        tmpl = env.from_string('{{ cls|attr("__subclasses__")() }}')
97        pytest.raises(SecurityError, tmpl.render, cls=int)
98
99    def test_binary_operator_intercepting(self, env):
100        def disable_op(left, right):
101            raise TemplateRuntimeError("that operator so does not work")
102
103        for expr, ctx, rv in ("1 + 2", {}, "3"), ("a + 2", {"a": 2}, "4"):
104            env = SandboxedEnvironment()
105            env.binop_table["+"] = disable_op
106            t = env.from_string(f"{{{{ {expr} }}}}")
107            assert t.render(ctx) == rv
108            env.intercepted_binops = frozenset(["+"])
109            t = env.from_string(f"{{{{ {expr} }}}}")
110            with pytest.raises(TemplateRuntimeError):
111                t.render(ctx)
112
113    def test_unary_operator_intercepting(self, env):
114        def disable_op(arg):
115            raise TemplateRuntimeError("that operator so does not work")
116
117        for expr, ctx, rv in ("-1", {}, "-1"), ("-a", {"a": 2}, "-2"):
118            env = SandboxedEnvironment()
119            env.unop_table["-"] = disable_op
120            t = env.from_string(f"{{{{ {expr} }}}}")
121            assert t.render(ctx) == rv
122            env.intercepted_unops = frozenset(["-"])
123            t = env.from_string(f"{{{{ {expr} }}}}")
124            with pytest.raises(TemplateRuntimeError):
125                t.render(ctx)
126
127
128class TestStringFormat:
129    def test_basic_format_safety(self):
130        env = SandboxedEnvironment()
131        t = env.from_string('{{ "a{0.__class__}b".format(42) }}')
132        assert t.render() == "ab"
133
134    def test_basic_format_all_okay(self):
135        env = SandboxedEnvironment()
136        t = env.from_string('{{ "a{0.foo}b".format({"foo": 42}) }}')
137        assert t.render() == "a42b"
138
139    def test_safe_format_safety(self):
140        env = SandboxedEnvironment()
141        t = env.from_string('{{ ("a{0.__class__}b{1}"|safe).format(42, "<foo>") }}')
142        assert t.render() == "ab&lt;foo&gt;"
143
144    def test_safe_format_all_okay(self):
145        env = SandboxedEnvironment()
146        t = env.from_string('{{ ("a{0.foo}b{1}"|safe).format({"foo": 42}, "<foo>") }}')
147        assert t.render() == "a42b&lt;foo&gt;"
148
149    def test_empty_braces_format(self):
150        env = SandboxedEnvironment()
151        t1 = env.from_string('{{ ("a{}b{}").format("foo", "42")}}')
152        t2 = env.from_string('{{ ("a{}b{}"|safe).format(42, "<foo>") }}')
153        assert t1.render() == "afoob42"
154        assert t2.render() == "a42b&lt;foo&gt;"
155
156
157class TestStringFormatMap:
158    def test_basic_format_safety(self):
159        env = SandboxedEnvironment()
160        t = env.from_string('{{ "a{x.__class__}b".format_map({"x":42}) }}')
161        assert t.render() == "ab"
162
163    def test_basic_format_all_okay(self):
164        env = SandboxedEnvironment()
165        t = env.from_string('{{ "a{x.foo}b".format_map({"x":{"foo": 42}}) }}')
166        assert t.render() == "a42b"
167
168    def test_safe_format_all_okay(self):
169        env = SandboxedEnvironment()
170        t = env.from_string(
171            '{{ ("a{x.foo}b{y}"|safe).format_map({"x":{"foo": 42}, "y":"<foo>"}) }}'
172        )
173        assert t.render() == "a42b&lt;foo&gt;"
174