mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
commit
2a1c577a42
3 changed files with 93 additions and 85 deletions
|
@ -169,7 +169,7 @@ def app_info(app, full=False):
|
||||||
ret["from_catalog"] = _load_apps_catalog()["apps"].get(absolute_app_name, {})
|
ret["from_catalog"] = _load_apps_catalog()["apps"].get(absolute_app_name, {})
|
||||||
ret["upgradable"] = _app_upgradable(ret)
|
ret["upgradable"] = _app_upgradable(ret)
|
||||||
|
|
||||||
ret["is_webapp"] = ("domain" in settings and "path" in settings)
|
ret["is_webapp"] = "domain" in settings and "path" in settings
|
||||||
|
|
||||||
ret["supports_change_url"] = os.path.exists(
|
ret["supports_change_url"] = os.path.exists(
|
||||||
os.path.join(setting_path, "scripts", "change_url")
|
os.path.join(setting_path, "scripts", "change_url")
|
||||||
|
|
|
@ -15,7 +15,7 @@ from yunohost.utils.config import (
|
||||||
PathQuestion,
|
PathQuestion,
|
||||||
BooleanQuestion,
|
BooleanQuestion,
|
||||||
FileQuestion,
|
FileQuestion,
|
||||||
evaluate_simple_js_expression
|
evaluate_simple_js_expression,
|
||||||
)
|
)
|
||||||
from yunohost.utils.error import YunohostError, YunohostValidationError
|
from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||||
|
|
||||||
|
@ -2095,97 +2095,95 @@ def test_normalize_path():
|
||||||
assert PathQuestion.normalize("macnuggets/") == "/macnuggets"
|
assert PathQuestion.normalize("macnuggets/") == "/macnuggets"
|
||||||
assert PathQuestion.normalize("////macnuggets///") == "/macnuggets"
|
assert PathQuestion.normalize("////macnuggets///") == "/macnuggets"
|
||||||
|
|
||||||
|
|
||||||
def test_simple_evaluate():
|
def test_simple_evaluate():
|
||||||
context = {
|
context = {
|
||||||
'a1': 1,
|
"a1": 1,
|
||||||
'b2': 2,
|
"b2": 2,
|
||||||
'c10': 10,
|
"c10": 10,
|
||||||
'foo': 'bar',
|
"foo": "bar",
|
||||||
'comp': '1>2',
|
"comp": "1>2",
|
||||||
'empty': '',
|
"empty": "",
|
||||||
'lorem': 'Lorem ipsum dolor et si qua met!',
|
"lorem": "Lorem ipsum dolor et si qua met!",
|
||||||
'warning': 'Warning! This sentence will fail!',
|
"warning": "Warning! This sentence will fail!",
|
||||||
'quote': "Je s'apelle Groot",
|
"quote": "Je s'apelle Groot",
|
||||||
'and_': '&&',
|
"and_": "&&",
|
||||||
'object': { 'a': 'Security risk' }
|
"object": {"a": "Security risk"},
|
||||||
}
|
}
|
||||||
supported = {
|
supported = {
|
||||||
'42': 42,
|
"42": 42,
|
||||||
'9.5': 9.5,
|
"9.5": 9.5,
|
||||||
"'bopbidibopbopbop'": 'bopbidibopbopbop',
|
"'bopbidibopbopbop'": "bopbidibopbopbop",
|
||||||
'true': True,
|
"true": True,
|
||||||
'false': False,
|
"false": False,
|
||||||
'null': None,
|
"null": None,
|
||||||
|
|
||||||
# Math
|
# Math
|
||||||
'1 * (2 + 3 * (4 - 3))': 5,
|
"1 * (2 + 3 * (4 - 3))": 5,
|
||||||
'1 * (2 + 3 * (4 - 3)) > 10 - 2 || 3 * 2 > 9 - 2 * 3': True,
|
"1 * (2 + 3 * (4 - 3)) > 10 - 2 || 3 * 2 > 9 - 2 * 3": True,
|
||||||
'(9 - 2) * 3 - 10': 11,
|
"(9 - 2) * 3 - 10": 11,
|
||||||
'12 - 2 * -2 + (3 - 4) * 3.1': 12.9,
|
"12 - 2 * -2 + (3 - 4) * 3.1": 12.9,
|
||||||
'9 / 12 + 12 * 3 - 5': 31.75,
|
"9 / 12 + 12 * 3 - 5": 31.75,
|
||||||
'9 / 12 + 12 * (3 - 5)': -23.25,
|
"9 / 12 + 12 * (3 - 5)": -23.25,
|
||||||
'12 > 13.1': False,
|
"12 > 13.1": False,
|
||||||
'12 < 14': True,
|
"12 < 14": True,
|
||||||
'12 <= 14': True,
|
"12 <= 14": True,
|
||||||
'12 >= 14': False,
|
"12 >= 14": False,
|
||||||
'12 == 14': False,
|
"12 == 14": False,
|
||||||
'12 % 5 > 3': False,
|
"12 % 5 > 3": False,
|
||||||
'12 != 14': True,
|
"12 != 14": True,
|
||||||
'9 - 1 > 10 && 3 * 5 > 10': False,
|
"9 - 1 > 10 && 3 * 5 > 10": False,
|
||||||
'9 - 1 > 10 || 3 * 5 > 10': True,
|
"9 - 1 > 10 || 3 * 5 > 10": True,
|
||||||
'a1 > 0 || a1 < -12': True,
|
"a1 > 0 || a1 < -12": True,
|
||||||
'a1 > 0 && a1 < -12': False,
|
"a1 > 0 && a1 < -12": False,
|
||||||
'a1 + 1 > 0 && -a1 > -12': True,
|
"a1 + 1 > 0 && -a1 > -12": True,
|
||||||
'-(a1 + 1) < 0 || -(a1 + 2) > -12': True,
|
"-(a1 + 1) < 0 || -(a1 + 2) > -12": True,
|
||||||
'-a1 * 2': -2,
|
"-a1 * 2": -2,
|
||||||
'(9 - 2) * 3 - c10': 11,
|
"(9 - 2) * 3 - c10": 11,
|
||||||
'(9 - b2) * 3 - c10': 11,
|
"(9 - b2) * 3 - c10": 11,
|
||||||
'c10 > b2': True,
|
"c10 > b2": True,
|
||||||
|
|
||||||
# String
|
# String
|
||||||
"foo == 'bar'":True,
|
"foo == 'bar'": True,
|
||||||
"foo != 'bar'":False,
|
"foo != 'bar'": False,
|
||||||
'foo == "bar" && 1 > 0':True,
|
'foo == "bar" && 1 > 0': True,
|
||||||
'!!foo': True,
|
"!!foo": True,
|
||||||
'!foo': False,
|
"!foo": False,
|
||||||
'foo': 'bar',
|
"foo": "bar",
|
||||||
'!(foo > "baa") || 1 > 2': False,
|
'!(foo > "baa") || 1 > 2': False,
|
||||||
'!(foo > "baa") || 1 < 2': True,
|
'!(foo > "baa") || 1 < 2': True,
|
||||||
'empty == ""': True,
|
'empty == ""': True,
|
||||||
'1 == "1"': True,
|
'1 == "1"': True,
|
||||||
'1.0 == "1"': True,
|
'1.0 == "1"': True,
|
||||||
'1 == "aaa"': False,
|
'1 == "aaa"': False,
|
||||||
"'I am ' + b2 + ' years'": 'I am 2 years',
|
"'I am ' + b2 + ' years'": "I am 2 years",
|
||||||
"quote == 'Je s\\'apelle Groot'": True,
|
"quote == 'Je s\\'apelle Groot'": True,
|
||||||
"lorem == 'Lorem ipsum dolor et si qua met!'": True,
|
"lorem == 'Lorem ipsum dolor et si qua met!'": True,
|
||||||
"and_ == '&&'": True,
|
"and_ == '&&'": True,
|
||||||
"warning == 'Warning! This sentence will fail!'": True,
|
"warning == 'Warning! This sentence will fail!'": True,
|
||||||
|
|
||||||
# Match
|
# Match
|
||||||
"match(lorem, '^Lorem [ia]psumE?')": bool,
|
"match(lorem, '^Lorem [ia]psumE?')": bool,
|
||||||
"match(foo, '^Lorem [ia]psumE?')": None,
|
"match(foo, '^Lorem [ia]psumE?')": None,
|
||||||
"match(lorem, '^Lorem [ia]psumE?') && 1 == 1": bool,
|
"match(lorem, '^Lorem [ia]psumE?') && 1 == 1": bool,
|
||||||
|
|
||||||
# No code
|
# No code
|
||||||
"": False,
|
"": False,
|
||||||
" ": False,
|
" ": False,
|
||||||
}
|
}
|
||||||
trigger_errors = {
|
trigger_errors = {
|
||||||
"object.a": YunohostError, # Keep unsupported, for security reasons
|
"object.a": YunohostError, # Keep unsupported, for security reasons
|
||||||
'a1 ** b2': YunohostError, # Keep unsupported, for security reasons
|
"a1 ** b2": YunohostError, # Keep unsupported, for security reasons
|
||||||
'().__class__.__bases__[0].__subclasses__()': YunohostError, # Very dangerous code
|
"().__class__.__bases__[0].__subclasses__()": YunohostError, # Very dangerous code
|
||||||
'a1 > 11 ? 1 : 0': SyntaxError,
|
"a1 > 11 ? 1 : 0": SyntaxError,
|
||||||
'c10 > b2 == false': YunohostError, # JS and Python doesn't do the same thing for this situation
|
"c10 > b2 == false": YunohostError, # JS and Python doesn't do the same thing for this situation
|
||||||
'c10 > b2 == true': YunohostError,
|
"c10 > b2 == true": YunohostError,
|
||||||
}
|
}
|
||||||
|
|
||||||
for expression, result in supported.items():
|
for expression, result in supported.items():
|
||||||
if result == bool:
|
if result == bool:
|
||||||
assert bool(evaluate_simple_js_expression(expression, context)), expression
|
assert bool(evaluate_simple_js_expression(expression, context)), expression
|
||||||
else:
|
else:
|
||||||
assert evaluate_simple_js_expression(expression, context) == result, expression
|
assert (
|
||||||
|
evaluate_simple_js_expression(expression, context) == result
|
||||||
|
), expression
|
||||||
|
|
||||||
for expression, error in trigger_errors.items():
|
for expression, error in trigger_errors.items():
|
||||||
with pytest.raises(error):
|
with pytest.raises(error):
|
||||||
evaluate_simple_js_expression(expression, context)
|
evaluate_simple_js_expression(expression, context)
|
||||||
|
|
||||||
|
|
|
@ -55,24 +55,24 @@ def evaluate_simple_ast(node, context={}):
|
||||||
operators = {
|
operators = {
|
||||||
ast.Not: op.not_,
|
ast.Not: op.not_,
|
||||||
ast.Mult: op.mul,
|
ast.Mult: op.mul,
|
||||||
ast.Div: op.truediv, # number
|
ast.Div: op.truediv, # number
|
||||||
ast.Mod: op.mod, # number
|
ast.Mod: op.mod, # number
|
||||||
ast.Add: op.add, #str
|
ast.Add: op.add, # str
|
||||||
ast.Sub: op.sub, #number
|
ast.Sub: op.sub, # number
|
||||||
ast.USub: op.neg, # Negative number
|
ast.USub: op.neg, # Negative number
|
||||||
ast.Gt: op.gt,
|
ast.Gt: op.gt,
|
||||||
ast.Lt: op.lt,
|
ast.Lt: op.lt,
|
||||||
ast.GtE: op.ge,
|
ast.GtE: op.ge,
|
||||||
ast.LtE: op.le,
|
ast.LtE: op.le,
|
||||||
ast.Eq: op.eq,
|
ast.Eq: op.eq,
|
||||||
ast.NotEq: op.ne
|
ast.NotEq: op.ne,
|
||||||
}
|
}
|
||||||
context['true'] = True
|
context["true"] = True
|
||||||
context['false'] = False
|
context["false"] = False
|
||||||
context['null'] = None
|
context["null"] = None
|
||||||
|
|
||||||
# Variable
|
# Variable
|
||||||
if isinstance(node, ast.Name): # Variable
|
if isinstance(node, ast.Name): # Variable
|
||||||
return context[node.id]
|
return context[node.id]
|
||||||
|
|
||||||
# Python <=3.7 String
|
# Python <=3.7 String
|
||||||
|
@ -88,14 +88,16 @@ def evaluate_simple_ast(node, context={}):
|
||||||
return node.value
|
return node.value
|
||||||
|
|
||||||
# + - * / %
|
# + - * / %
|
||||||
elif isinstance(node, ast.BinOp) and type(node.op) in operators: # <left> <operator> <right>
|
elif (
|
||||||
|
isinstance(node, ast.BinOp) and type(node.op) in operators
|
||||||
|
): # <left> <operator> <right>
|
||||||
left = evaluate_simple_ast(node.left, context)
|
left = evaluate_simple_ast(node.left, context)
|
||||||
right = evaluate_simple_ast(node.right, context)
|
right = evaluate_simple_ast(node.right, context)
|
||||||
if type(node.op) == ast.Add:
|
if type(node.op) == ast.Add:
|
||||||
if isinstance(left, str) or isinstance(right, str): # support 'I am ' + 42
|
if isinstance(left, str) or isinstance(right, str): # support 'I am ' + 42
|
||||||
left = str(left)
|
left = str(left)
|
||||||
right = str(right)
|
right = str(right)
|
||||||
elif type(left) != type(right): # support "111" - "1" -> 110
|
elif type(left) != type(right): # support "111" - "1" -> 110
|
||||||
left = float(left)
|
left = float(left)
|
||||||
right = float(right)
|
right = float(right)
|
||||||
|
|
||||||
|
@ -104,7 +106,9 @@ def evaluate_simple_ast(node, context={}):
|
||||||
# Comparison
|
# Comparison
|
||||||
# JS and Python don't give the same result for multi operators
|
# JS and Python don't give the same result for multi operators
|
||||||
# like True == 10 > 2.
|
# like True == 10 > 2.
|
||||||
elif isinstance(node, ast.Compare) and len(node.comparators) == 1: # <left> <ops> <comparators>
|
elif (
|
||||||
|
isinstance(node, ast.Compare) and len(node.comparators) == 1
|
||||||
|
): # <left> <ops> <comparators>
|
||||||
left = evaluate_simple_ast(node.left, context)
|
left = evaluate_simple_ast(node.left, context)
|
||||||
right = evaluate_simple_ast(node.comparators[0], context)
|
right = evaluate_simple_ast(node.comparators[0], context)
|
||||||
operator = node.ops[0]
|
operator = node.ops[0]
|
||||||
|
@ -116,11 +120,11 @@ def evaluate_simple_ast(node, context={}):
|
||||||
return type(operator) == ast.NotEq
|
return type(operator) == ast.NotEq
|
||||||
try:
|
try:
|
||||||
return operators[type(operator)](left, right)
|
return operators[type(operator)](left, right)
|
||||||
except TypeError: # support "e" > 1 -> False like in JS
|
except TypeError: # support "e" > 1 -> False like in JS
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# and / or
|
# and / or
|
||||||
elif isinstance(node, ast.BoolOp): # <op> <values>
|
elif isinstance(node, ast.BoolOp): # <op> <values>
|
||||||
for value in node.values:
|
for value in node.values:
|
||||||
value = evaluate_simple_ast(value, context)
|
value = evaluate_simple_ast(value, context)
|
||||||
if isinstance(node.op, ast.And) and not value:
|
if isinstance(node.op, ast.And) and not value:
|
||||||
|
@ -130,20 +134,22 @@ def evaluate_simple_ast(node, context={}):
|
||||||
return isinstance(node.op, ast.And)
|
return isinstance(node.op, ast.And)
|
||||||
|
|
||||||
# not / USub (it's negation number -\d)
|
# not / USub (it's negation number -\d)
|
||||||
elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1
|
elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1
|
||||||
return operators[type(node.op)](evaluate_simple_ast(node.operand, context))
|
return operators[type(node.op)](evaluate_simple_ast(node.operand, context))
|
||||||
|
|
||||||
# match function call
|
# match function call
|
||||||
elif isinstance(node, ast.Call) and node.func.__dict__.get('id') == 'match':
|
elif isinstance(node, ast.Call) and node.func.__dict__.get("id") == "match":
|
||||||
return re.match(
|
return re.match(
|
||||||
evaluate_simple_ast(node.args[1], context),
|
evaluate_simple_ast(node.args[1], context), context[node.args[0].id]
|
||||||
context[node.args[0].id]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Unauthorized opcode
|
# Unauthorized opcode
|
||||||
else:
|
else:
|
||||||
opcode = str(type(node))
|
opcode = str(type(node))
|
||||||
raise YunohostError(f"Unauthorize opcode '{opcode}' in visible attribute", raw_msg=True)
|
raise YunohostError(
|
||||||
|
f"Unauthorize opcode '{opcode}' in visible attribute", raw_msg=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def js_to_python(expr):
|
def js_to_python(expr):
|
||||||
in_string = None
|
in_string = None
|
||||||
|
@ -162,7 +168,7 @@ def js_to_python(expr):
|
||||||
|
|
||||||
# If we are not in a string, replace operators
|
# If we are not in a string, replace operators
|
||||||
elif not in_string:
|
elif not in_string:
|
||||||
if char == "!" and expr[i +1] != "=":
|
if char == "!" and expr[i + 1] != "=":
|
||||||
char = "not "
|
char = "not "
|
||||||
elif char in "|&" and py_expr[-1:] == char:
|
elif char in "|&" and py_expr[-1:] == char:
|
||||||
py_expr = py_expr[:-1]
|
py_expr = py_expr[:-1]
|
||||||
|
@ -171,15 +177,17 @@ def js_to_python(expr):
|
||||||
# Determine if next loop will be in escaped mode
|
# Determine if next loop will be in escaped mode
|
||||||
escaped = char == "\\" and not escaped
|
escaped = char == "\\" and not escaped
|
||||||
py_expr += char
|
py_expr += char
|
||||||
i+=1
|
i += 1
|
||||||
return py_expr
|
return py_expr
|
||||||
|
|
||||||
|
|
||||||
def evaluate_simple_js_expression(expr, context={}):
|
def evaluate_simple_js_expression(expr, context={}):
|
||||||
if not expr.strip():
|
if not expr.strip():
|
||||||
return False
|
return False
|
||||||
node = ast.parse(js_to_python(expr), mode="eval").body
|
node = ast.parse(js_to_python(expr), mode="eval").body
|
||||||
return evaluate_simple_ast(node, context)
|
return evaluate_simple_ast(node, context)
|
||||||
|
|
||||||
|
|
||||||
class ConfigPanel:
|
class ConfigPanel:
|
||||||
def __init__(self, config_path, save_path=None):
|
def __init__(self, config_path, save_path=None):
|
||||||
self.config_path = config_path
|
self.config_path = config_path
|
||||||
|
@ -648,7 +656,9 @@ class Question(object):
|
||||||
|
|
||||||
def ask_if_needed(self):
|
def ask_if_needed(self):
|
||||||
|
|
||||||
if self.visible and not evaluate_simple_js_expression(self.visible, context=self.context):
|
if self.visible and not evaluate_simple_js_expression(
|
||||||
|
self.visible, context=self.context
|
||||||
|
):
|
||||||
# FIXME There could be several use case if the question is not displayed:
|
# FIXME There could be several use case if the question is not displayed:
|
||||||
# - we doesn't want to give a specific value
|
# - we doesn't want to give a specific value
|
||||||
# - we want to keep the previous value
|
# - we want to keep the previous value
|
||||||
|
|
Loading…
Add table
Reference in a new issue