From a5819e9b4835bb0ed2f09abe03a78a165239c44c Mon Sep 17 00:00:00 2001 From: Ullauri Date: Mon, 31 Dec 2018 00:31:14 -0500 Subject: [PATCH] replacing eval with ast parse --- ihatemoney/forms.py | 9 +++------ ihatemoney/tests/tests.py | 2 +- ihatemoney/utils.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/ihatemoney/forms.py b/ihatemoney/forms.py index 8b7f6efd..b16d131f 100644 --- a/ihatemoney/forms.py +++ b/ihatemoney/forms.py @@ -14,7 +14,7 @@ from jinja2 import Markup import email_validator from ihatemoney.models import Project, Person -from ihatemoney.utils import slugify +from ihatemoney.utils import slugify, eval_arithmetic_expression def get_billform_for(project, set_default=True, **kwargs): @@ -54,15 +54,12 @@ class CalculatorStringField(StringField): def process_formdata(self, valuelist): if valuelist: error_msg = "Not a valid amount or expression" - value = str(valuelist[0]).replace(" ", "").replace(",", ".") + value = str(valuelist[0]).replace(",", ".") if not match(r'^[ 0-9\.\+\-\*/\(\)]{0,50}$', value) or "**" in value: raise ValueError(error_msg) - try: - valuelist[0] = str(eval(value, {"__builtins__": None}, {})) - except (SyntaxError, NameError, TypeError, ZeroDivisionError): - raise ValueError(error_msg) + valuelist[0] = str(eval_arithmetic_expression(value)) return super(CalculatorStringField, self).process_formdata(valuelist) diff --git a/ihatemoney/tests/tests.py b/ihatemoney/tests/tests.py index d7af9d63..980f4d13 100644 --- a/ihatemoney/tests/tests.py +++ b/ihatemoney/tests/tests.py @@ -1399,7 +1399,7 @@ class APITestCase(IhatemoneyTestCase): "(20 + 2", # invalid expression "20/0", # invalid calc "9999**99999999999999999", # exponents - "2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2" # greater than 50 chars + "2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2", # greater than 50 chars, ] for amount in erroneous_amounts: diff --git a/ihatemoney/utils.py b/ihatemoney/utils.py index ec228343..f75219e2 100644 --- a/ihatemoney/utils.py +++ b/ihatemoney/utils.py @@ -1,5 +1,7 @@ import base64 import re +import ast +import operator from io import BytesIO, StringIO import jinja2 @@ -206,3 +208,33 @@ class IhmJSONEncoder(JSONEncoder): except ImportError: pass return JSONEncoder.default(self, o) + + +def eval_arithmetic_expression(expr): + def _eval(node): + # supported operators + operators = { + ast.Add: operator.add, + ast.Sub: operator.sub, + ast.Mult: operator.mul, + ast.Div: operator.truediv, + ast.USub: operator.neg, + } + + if isinstance(node, ast.Num): # + return node.n + elif isinstance(node, ast.BinOp): # + return operators[type(node.op)](_eval(node.left), _eval(node.right)) + elif isinstance(node, ast.UnaryOp): # e.g., -1 + return operators[type(node.op)](_eval(node.operand)) + else: + raise TypeError(node) + + expr = str(expr) + + try: + result = _eval(ast.parse(expr, mode='eval').body) + except (SyntaxError, TypeError, ZeroDivisionError, KeyError): + raise ValueError("Error evaluating expression: {}".format(expr)) + + return result