diff --git a/searx/preferences.py b/searx/preferences.py index 6e7d4a5e1..0c59effc4 100644 --- a/searx/preferences.py +++ b/searx/preferences.py @@ -9,6 +9,7 @@ from base64 import urlsafe_b64encode, urlsafe_b64decode from zlib import compress, decompress from urllib.parse import parse_qs, urlencode from typing import Iterable, Dict, List, Optional +from collections import OrderedDict import flask import babel @@ -24,6 +25,18 @@ from searx.engines import DEFAULT_CATEGORY COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 5 # 5 years DOI_RESOLVERS = list(settings['doi_resolvers']) +MAP_STR2BOOL: Dict[str, bool] = OrderedDict( + [ + ('0', False), + ('1', True), + ('on', True), + ('off', False), + ('True', True), + ('False', False), + ('none', False), + ] +) + class ValidationException(Exception): @@ -199,6 +212,26 @@ class MapSetting(Setting): resp.set_cookie(name, self.key, max_age=COOKIE_MAX_AGE) +class BooleanSetting(Setting): + """Setting of a boolean value that has to be translated in order to be storable""" + + def normalized_str(self, val): + for v_str, v_obj in MAP_STR2BOOL.items(): + if val == v_obj: + return v_str + raise ValueError("Invalid value: %s (%s) is not a boolean!" % (repr(val), type(val))) + + def parse(self, data: str): + """Parse and validate ``data`` and store the result at ``self.value``""" + self.value = MAP_STR2BOOL[data] + self.key = self.normalized_str(self.value) # pylint: disable=attribute-defined-outside-init + + def save(self, name: str, resp: flask.Response): + """Save cookie ``name`` in the HTTP response object""" + if hasattr(self, 'key'): + resp.set_cookie(name, self.key, max_age=COOKIE_MAX_AGE) + + class BooleanChoices: """Maps strings to booleans that are either true or false.""" @@ -375,17 +408,9 @@ class Preferences: locked=is_locked('autocomplete'), choices=list(autocomplete.backends.keys()) + [''] ), - 'image_proxy': MapSetting( + 'image_proxy': BooleanSetting( settings['server']['image_proxy'], - locked=is_locked('image_proxy'), - map={ - '': settings['server']['image_proxy'], - '0': False, - '1': True, - 'True': True, - 'False': False, - 'on': True - } + locked=is_locked('image_proxy') ), 'method': EnumStringSetting( settings['server']['method'], @@ -406,16 +431,9 @@ class Preferences: locked=is_locked('theme'), choices=themes ), - 'results_on_new_tab': MapSetting( + 'results_on_new_tab': BooleanSetting( settings['ui']['results_on_new_tab'], - locked=is_locked('results_on_new_tab'), - map={ - '0': False, - '1': True, - 'False': False, - 'True': True, - 'on': True - } + locked=is_locked('results_on_new_tab') ), 'doi_resolver': MultipleChoiceSetting( [settings['default_doi_resolver'], ], @@ -427,51 +445,21 @@ class Preferences: locked=is_locked('simple_style'), choices=['', 'auto', 'light', 'dark'] ), - 'center_alignment': MapSetting( + 'center_alignment': BooleanSetting( settings['ui']['center_alignment'], - locked=is_locked('center_alignment'), - map={ - '0': False, - '1': True, - 'False': False, - 'True': True, - 'on': True - } + locked=is_locked('center_alignment') ), - 'advanced_search': MapSetting( + 'advanced_search': BooleanSetting( settings['ui']['advanced_search'], - locked=is_locked('advanced_search'), - map={ - '0': False, - '1': True, - 'False': False, - 'True': True, - 'on': True, - } + locked=is_locked('advanced_search') ), - 'query_in_title': MapSetting( + 'query_in_title': BooleanSetting( settings['ui']['query_in_title'], - locked=is_locked('query_in_title'), - map={ - '': settings['ui']['query_in_title'], - '0': False, - '1': True, - 'True': True, - 'False': False, - 'on': True - } + locked=is_locked('query_in_title') ), - 'infinite_scroll': MapSetting( + 'infinite_scroll': BooleanSetting( settings['ui']['infinite_scroll'], - locked=is_locked('infinite_scroll'), - map={ - '': settings['ui']['infinite_scroll'], - '0': False, - '1': True, - 'True': True, - 'False': False, - 'on': True - } + locked=is_locked('infinite_scroll') ), # fmt: on } @@ -534,6 +522,13 @@ class Preferences: disabled_engines = [] enabled_categories = [] disabled_plugins = [] + + # boolean preferences are not sent by the form if they're false, + # so we have to add them as false manually if they're not sent (then they would be true) + for key, setting in self.key_value_settings.items(): + if key not in input_data.keys() and isinstance(setting, BooleanSetting): + input_data[key] = 'False' + for user_setting_name, user_setting in input_data.items(): if user_setting_name in self.key_value_settings: self.key_value_settings[user_setting_name].parse(user_setting)