Skip to content

Commit e7760d8

Browse files
committed
add safe string templating in case message contains bracketed words
1 parent ad0a52a commit e7760d8

2 files changed

Lines changed: 27 additions & 4 deletions

File tree

pythonwhat/State.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ast
22
import inspect
3+
import string
34
from copy import copy
45
from pythonwhat.parsing import TargetVars, FunctionParser, ObjectAccessParser, parser_dict
56
from pythonwhat.Reporter import Reporter
@@ -31,6 +32,17 @@ def __len__(self):
3132
return len(self._items)
3233

3334

35+
class MsgFormatter(string.Formatter):
36+
def __init__(self, default='{{{0}}}'):
37+
self.default=default
38+
39+
def get_value(self, key, args, kwds):
40+
if isinstance(key, str):
41+
return kwds.get(key, self.default.format(key))
42+
else:
43+
Formatter.get_value(key, args, kwds)
44+
45+
3446
class State(object):
3547
"""State of the SCT environment.
3648
@@ -43,6 +55,7 @@ def __init__(self,
4355
student_context=None, solution_context=None,
4456
student_parts=None, solution_parts=None,
4557
highlight = None, messages=None,
58+
msg_formatter = None,
4659
**kwargs):
4760

4861
# Set basic fields from kwargs
@@ -70,6 +83,8 @@ def __init__(self,
7083

7184
self.highlight = self.student_tree if highlight is None else highlight
7285

86+
self.msg_formatter = MsgFormatter() if msg_formatter is None else msg_formatter
87+
7388
self.converters = get_manual_converters() # accessed only from root state
7489

7590
self.fun_usage = {}
@@ -108,10 +123,11 @@ def build_message(self, tail="", fmt_kwargs=None):
108123
msgs = self.messages[:] + [{'msg': tail or "", 'kwargs':fmt_kwargs}]
109124
# format messages in list, by iterating over previous, current, and next message
110125
for prev_d, d, next_d in zip([{}, *msgs[:-1]], msgs, [*msgs[1:], {}]):
111-
out = d['msg'].format(parent = prev_d.get('kwargs'),
112-
child = next_d.get('kwargs'),
113-
this = d['kwargs'],
114-
**d['kwargs'])
126+
out = self.msg_formatter.format(d['msg'],
127+
parent = prev_d.get('kwargs'),
128+
child = next_d.get('kwargs'),
129+
this = d['kwargs'],
130+
**d['kwargs'])
115131
out_list.append(out)
116132

117133
return "".join(out_list)
@@ -163,6 +179,7 @@ def to_child_state(self, student_subtree, solution_subtree,
163179
solution_process = self.solution_process,
164180
raw_student_output = self.raw_student_output,
165181
pre_exercise_tree = self.pre_exercise_tree,
182+
msg_formatter = self.msg_formatter,
166183
student_tree = student_subtree,
167184
solution_tree = solution_subtree,
168185
student_parts = student_parts,

tests/test_test_object.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ def test_pass(self):
2828
sct_payload = helper.run(self.data)
2929
self.assertTrue(sct_payload['correct'])
3030

31+
def test_fail_with_unsafe_msg(self):
32+
self.data["DC_CODE"] = ""
33+
self.data["DC_SCT"] = "test_object('x', undefined_msg = '`{a_bad_template_var}`')"
34+
sct_payload = helper.run(self.data)
35+
self.assertEqual('<code>{a_bad_template_var}</code>', sct_payload['message'])
36+
3137
class TestObjectStepByStepCustom(unittest.TestCase):
3238

3339
def setUp(self):

0 commit comments

Comments
 (0)