Skip to content

Commit

Permalink
sandbox str.format_map
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko authored and davidism committed Apr 6, 2019
1 parent 78d2f67 commit a2a6c93
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 3 deletions.
17 changes: 14 additions & 3 deletions jinja2/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def __len__(self):
def inspect_format_method(callable):
if not isinstance(callable, (types.MethodType,
types.BuiltinMethodType)) or \
callable.__name__ != 'format':
callable.__name__ not in ('format', 'format_map'):
return None
obj = callable.__self__
if isinstance(obj, string_types):
Expand Down Expand Up @@ -402,14 +402,25 @@ def unsafe_undefined(self, obj, attribute):
obj.__class__.__name__
), name=attribute, obj=obj, exc=SecurityError)

def format_string(self, s, args, kwargs):
def format_string(self, s, args, kwargs, format_func=None):
"""If a format call is detected, then this is routed through this
method so that our safety sandbox can be used for it.
"""
if isinstance(s, Markup):
formatter = SandboxedEscapeFormatter(self, s.escape)
else:
formatter = SandboxedFormatter(self)

if format_func is not None and format_func.__name__ == 'format_map':
if len(args) != 1 or kwargs:
raise TypeError(
'format_map() takes exactly one argument %d given'
% (len(args) + (kwargs is not None))
)

kwargs = args[0]
args = None

kwargs = _MagicFormatMapping(args, kwargs)
rv = formatter.vformat(s, args, kwargs)
return type(s)(rv)
Expand All @@ -418,7 +429,7 @@ def call(__self, __context, __obj, *args, **kwargs):
"""Call an object from sandboxed code."""
fmt = inspect_format_method(__obj)
if fmt is not None:
return __self.format_string(fmt, args, kwargs)
return __self.format_string(fmt, args, kwargs, __obj)

# the double prefixes are to avoid double keyword argument
# errors when proxying the call.
Expand Down
19 changes: 19 additions & 0 deletions tests/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,22 @@ 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&lt;foo&gt;'


@pytest.mark.sandbox
@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&lt;foo&gt;'

0 comments on commit a2a6c93

Please sign in to comment.