From 3b97f601be0437d170a71c07cba66b102f86e9bc Mon Sep 17 00:00:00 2001 From: std-odoo Date: Wed, 1 Dec 2021 17:30:51 +0100 Subject: [PATCH] [FIX] base: avoid variable name collision with functions in QWeb Bug === The arguments of the lambda function might be the same as other variables in the QWeb. In that case the template rendering will crash. To avoid this issue, we encapsulate the name of the argument to be sure it's not the same as other variable in the QWeb engine (so the name of the argument can no longer make the QWeb rendering crash) Task-2674716 Closes #78392. Forward-port of 96b09b127028e5c9a83c89acce86e1b6df904c6f --- odoo/addons/base/models/qweb.py | 26 +++++++++++++++++++------- odoo/addons/base/tests/test_qweb.py | 5 ++++- odoo/tools/safe_eval.py | 2 +- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/odoo/addons/base/models/qweb.py b/odoo/addons/base/models/qweb.py index e83236240bf..cd10dbfb026 100644 --- a/odoo/addons/base/models/qweb.py +++ b/odoo/addons/base/models/qweb.py @@ -295,7 +295,6 @@ class QWeb(object): globals_dict['Mapping'] = Mapping globals_dict['Markup'] = Markup globals_dict['escape'] = escape - globals_dict['type'] = type globals_dict['compile_options'] = options globals_dict.update(self._available_objects) return globals_dict @@ -410,7 +409,7 @@ class QWeb(object): code += f'.format({", ".join(values)})' return code - def _compile_expr_tokens(self, tokens, allowed_keys, raise_on_missing=False): + def _compile_expr_tokens(self, tokens, allowed_keys, argument_names=None, raise_on_missing=False): """ Transform the list of token coming into a python instruction in textual form by adding the namepaces for the dynamic values. @@ -436,6 +435,10 @@ class QWeb(object): index = 0 open_bracket_index = -1 bracket_depth = 0 + + argument_name = '_arg_%s__' + argument_names = argument_names or [] + while index < len(tokens): t = tokens[index] if t.exact_type in [token.LPAR, token.LSQB, token.LBRACE]: @@ -449,7 +452,7 @@ class QWeb(object): while i < len(tokens): t = tokens[i] if t.exact_type == token.NAME: - allowed_keys.append(t.string) + argument_names.append(t.string) elif t.exact_type == token.COMMA: pass elif t.exact_type == token.COLON: @@ -466,7 +469,7 @@ class QWeb(object): if t.exact_type == token.NAME: if t.string == 'in': break - allowed_keys.append(t.string) + argument_names.append(t.string) elif t.exact_type in [token.COMMA, token.LPAR, token.RPAR]: pass else: @@ -495,7 +498,12 @@ class QWeb(object): elif t.exact_type in [token.RPAR, token.RSQB, token.RBRACE]: bracket_depth -= 1 if bracket_depth == 0: - code = self._compile_expr_tokens(tokens[open_bracket_index + 1:index], list(allowed_keys), raise_on_missing) + code = self._compile_expr_tokens( + tokens[open_bracket_index + 1:index], + list(allowed_keys), + list(argument_names), + raise_on_missing, + ) code = tokens[open_bracket_index].string + code + t.string tokens[open_bracket_index:index + 1] = [tokenize.TokenInfo(token.QWEB, code, tokens[open_bracket_index].start, t.end, '')] index = open_bracket_index @@ -526,7 +534,9 @@ class QWeb(object): index += 1 while index < len(tokens): t = tokens[index] - if t.exact_type in [token.COMMA, token.NAME, token.COLON]: + if t.exact_type == token.NAME and t.string in argument_names: + code.append(argument_name % t.string) + if t.exact_type in [token.COMMA, token.COLON]: code.append(t.string) if t.exact_type == token.COLON: break @@ -535,6 +545,8 @@ class QWeb(object): pos = (t.end[0], 0) else: pos = t.end + elif string in argument_names: + code.append(argument_name % t.string) elif string in allowed_keys: code.append(string) elif index + 1 < len(tokens) and tokens[index + 1].exact_type == token.EQUAL: # function kw @@ -1048,7 +1060,7 @@ class QWeb(object): {t_foreach} = {self._compile_expr(expr_foreach)} or [] if isinstance({t_foreach}, Sized): values[{repr(expr_as + '_size')}] = {size} = len({t_foreach}) - elif type({t_foreach}) == int: + elif ({t_foreach}).__class__ == int: values[{repr(expr_as + '_size')}] = {size} = {t_foreach} {t_foreach} = range({size}) else: diff --git a/odoo/addons/base/tests/test_qweb.py b/odoo/addons/base/tests/test_qweb.py index f0d26e443c3..1d6d6e90d08 100644 --- a/odoo/addons/base/tests/test_qweb.py +++ b/odoo/addons/base/tests/test_qweb.py @@ -684,6 +684,7 @@ class TestQWebBasic(TransactionCase): ("fn(a=11, b=22) or a", {'a': 1, 'fn': lambda a,b: b}, 22), ("(lambda a: a)(5)", {}, 5), ("(lambda a: a[0])([5])", {}, 5), + ("(lambda test: len(test))('aaa')", {}, 3), ("{'a': lambda a: a[0], 'b': 3}['a']([5])", {}, 5), ("list(map(lambda a: a[0], r))", {'r': [(1,11), (2,22)]}, [1, 2]), ("z + (head or 'z')", {'z': 'a'}, "az"), @@ -693,8 +694,10 @@ class TestQWebBasic(TransactionCase): ("any({x == 5 for x in [1,2,3]})", {}, False), ("{x:y for x,y in [('a', 11),('b', 22)]}", {}, {'a': 11, 'b': 22}), ("[(y,x) for x,y in [(1, 11),(2, 22)]]", {}, [(11, 1), (22, 2)]), - ("(lambda a: a + 5)(a=x)", {'x': 10}, 15), + ("(lambda a: a + 5)(x)", {'x': 10}, 15), + ("(lambda a: a + x)(5)", {'x': 10}, 15), ("sum(x for x in range(4)) + ((x))", {'x': 10}, 16), + ("['test_' + x for x in ['a', 'b']]", {}, ['test_a', 'test_b']) ] IrQweb = self.env['ir.qweb'] diff --git a/odoo/tools/safe_eval.py b/odoo/tools/safe_eval.py index f60bb5e91b6..591659faf26 100644 --- a/odoo/tools/safe_eval.py +++ b/odoo/tools/safe_eval.py @@ -37,7 +37,7 @@ __all__ = ['test_expr', 'safe_eval', 'const_eval'] # lp:703841), does import time. _ALLOWED_MODULES = ['_strptime', 'math', 'time'] -_UNSAFE_ATTRIBUTES = ['f_builtins', 'f_globals', 'f_locals', 'gi_frame', +_UNSAFE_ATTRIBUTES = ['f_builtins', 'f_globals', 'f_locals', 'gi_frame', 'gi_code', 'co_code', 'func_globals'] def to_opcodes(opnames, _opmap=opmap):