diff --git a/requirements-dev.txt b/requirements-dev.txt index bfc4244dd50550b5bb203e12f8d82c36ed520829..e1cb212da86cb0f352db5193bbf87097b2e87414 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,3 +20,4 @@ linuxdoc==20211220 aiounittest==1.4.1 numexpr==2.8.1 werkzeug==2.0.3 +wrapt-timeout-decorator==1.3.8 diff --git a/requirements.txt b/requirements.txt index a91f92fc0c61680b8c947fe5e9d2d5cc67b3991e..31f48c2691c6f5a32ea4825df28b322b897ff31d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,3 +18,4 @@ redis==3.4.1 ring==0.7.3 numexpr==2.8.1 werkzeug==2.0.3 +wrapt-timeout-decorator==1.3.8 \ No newline at end of file diff --git a/searx/plugins/calculator.py b/searx/plugins/calculator.py index f8a7eefa631fbf3955595d1918fb0288d320b284..c0b5499f6843eeb080ed33c090313408988b43af 100644 --- a/searx/plugins/calculator.py +++ b/searx/plugins/calculator.py @@ -1,7 +1,7 @@ from searx import logger from numexpr import evaluate from flask_babel import gettext - +from wrapt_timeout_decorator import timeout name = gettext('Calculator') description = gettext('This plugin extends results when the query is a mathematical expression') @@ -13,12 +13,10 @@ def check_if_loaded(): logger.debug("initializing calculator plugin") -def is_really_big(query): - # For cases like 2**99999**9999 - if len(query.split("**")) >= 3: - return True - # Add more cases if needed - return False +# Set timeout so that the plugin doesn't hang for long computations +@timeout(5) +def calculate(query): + return evaluate(query).item() def post_search(request, search): @@ -27,8 +25,11 @@ def post_search(request, search): try: query = search.search_query.query.lower() unmodified_query = query + + # Replace all frequently used substitutes query = query.replace("x", "*") query = query.replace("^", "**") + query = query.replace("%", "*0.01") # Not going to compute if only one number is present try: @@ -45,18 +46,18 @@ def post_search(request, search): if len(query) > 30: return - # Not going to compute the result if the query is not within permissible range - if is_really_big(query): - raise OverflowError + # Multiply by float to upcast all numbers to floats + # https://numexpr.readthedocs.io/projects/NumExpr3/en/latest/user_guide.html#casting-rules + query += "*1.0" - value = evaluate(query).item() + value = calculate(query) if type(value) in (int, float): search.result_container.answers.clear() answer = "{} = {}".format(unmodified_query, value) - search.result_container.answers[answer] = {'answer': answer, 'calculator': True} - except (ZeroDivisionError, ValueError, FloatingPointError, MemoryError, OverflowError) as e: + search.result_container.answers['calculator'] = {'answer': answer, 'calculator': True} + except (ZeroDivisionError, ValueError, FloatingPointError, MemoryError, OverflowError, TimeoutError) as e: answer = gettext('Error') - search.result_container.answers[answer] = {'answer': answer, 'calculator': True} + search.result_container.answers['calculator'] = {'answer': answer, 'calculator': True} except Exception as e: logger.debug(e) diff --git a/tests/unit/test_plugins.py b/tests/unit/test_plugins.py index 9ef4cd6927397af525ab6d972ca1d6c759652a54..c4f323c2515d4fa68954425f3c96a85ce56523f9 100644 --- a/tests/unit/test_plugins.py +++ b/tests/unit/test_plugins.py @@ -137,3 +137,51 @@ class HashPluginTest(SearxTestCase): self.assertTrue('sha512 hash digest: ee26b0dd4af7e749aa1a8ee3c10ae9923f6' '18980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5' 'fa9ad8e6f57f50028a8ff' in search.result_container.answers['hash']['answer']) + + +class CalculatorPluginTest(SearxTestCase): + + def test_PluginStore_init(self): + store = plugins.PluginStore() + store.register(plugins.calculator) + + self.assertTrue(len(store.plugins) == 1) + + request = Mock(remote_addr='127.0.0.1') + request.headers.getlist.return_value = [] + + # True addition test + search = get_search_mock(query='2+2', pageno=1) + store.call(store.plugins, 'post_search', request, search) + self.assertTrue('4' + in search.result_container.answers['calculator']['answer']) + + # False addition test + search = get_search_mock(query='2+2', pageno=1) + store.call(store.plugins, 'post_search', request, search) + self.assertFalse('4' not + in search.result_container.answers['calculator']['answer']) + + # no result test + search = get_search_mock(query='2+2', pageno=2) + store.call(store.plugins, 'post_search', request, search) + self.assertFalse('calculator' + in search.result_container.answers) + + # no result test + search = get_search_mock(query='2+2/sdf', pageno=1) + store.call(store.plugins, 'post_search', request, search) + self.assertFalse('calculator' + in search.result_container.answers) + + # error result test + search = get_search_mock(query='2+2/0', pageno=1) + store.call(store.plugins, 'post_search', request, search) + self.assertTrue('Error' + in search.result_container.answers['calculator']['answer']) + + # error result test + search = get_search_mock(query='2**999999999**99999999', pageno=1) + store.call(store.plugins, 'post_search', request, search) + self.assertTrue('Error' + in search.result_container.answers['calculator']['answer'])