| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  | # SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | # lint: pylint | 
					
						
							| 
									
										
										
										
											2021-06-01 16:03:19 +02:00
										 |  |  | """Implementation of the default settings.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import typing | 
					
						
							|  |  |  | import numbers | 
					
						
							|  |  |  | import errno | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import logging | 
					
						
							|  |  |  | from os.path import dirname, abspath | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from searx.languages import language_codes as languages | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | searx_dir = abspath(dirname(__file__)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | logger = logging.getLogger('searx') | 
					
						
							|  |  |  | OUTPUT_FORMATS = ['html', 'csv', 'json', 'rss'] | 
					
						
							| 
									
										
										
										
											2021-10-21 10:41:57 +02:00
										 |  |  | LANGUAGE_CODES = ['all'] + list(l[0] for l in languages) | 
					
						
							| 
									
										
										
										
											2021-11-19 13:49:16 +01:00
										 |  |  | SIMPLE_STYLE = ('auto', 'light', 'dark') | 
					
						
							| 
									
										
										
										
											2022-01-04 18:00:45 +01:00
										 |  |  | CATEGORIES_AS_TABS = { | 
					
						
							|  |  |  |     'general': {}, | 
					
						
							|  |  |  |     'images': {}, | 
					
						
							|  |  |  |     'videos': {}, | 
					
						
							|  |  |  |     'news': {}, | 
					
						
							|  |  |  |     'map': {}, | 
					
						
							|  |  |  |     'music': {}, | 
					
						
							|  |  |  |     'it': {}, | 
					
						
							|  |  |  |     'science': {}, | 
					
						
							|  |  |  |     'files': {}, | 
					
						
							|  |  |  |     'social media': {}, | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  | STR_TO_BOOL = { | 
					
						
							|  |  |  |     '0': False, | 
					
						
							|  |  |  |     'false': False, | 
					
						
							|  |  |  |     'off': False, | 
					
						
							|  |  |  |     '1': True, | 
					
						
							|  |  |  |     'true': True, | 
					
						
							|  |  |  |     'on': True, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | _UNDEFINED = object() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-02 12:21:02 +02:00
										 |  |  | # compatibility | 
					
						
							|  |  |  | SEARX_ENVIRON_VARIABLES = { | 
					
						
							|  |  |  |     'SEARX_DISABLE_ETC_SETTINGS': 'SEARXNG_DISABLE_ETC_SETTINGS', | 
					
						
							|  |  |  |     'SEARX_SETTINGS_PATH': 'SEARXNG_SETTINGS_PATH', | 
					
						
							|  |  |  |     'SEARX_DEBUG': 'SEARXNG_DEBUG', | 
					
						
							|  |  |  |     'SEARX_PORT': 'SEARXNG_PORT', | 
					
						
							|  |  |  |     'SEARX_BIND_ADDRESS': 'SEARXNG_BIND_ADDRESS', | 
					
						
							|  |  |  |     'SEARX_SECRET': 'SEARXNG_SECRET', | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  | class SettingsValue: | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |     """Check and update a setting value""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__( | 
					
						
							|  |  |  |         self, | 
					
						
							|  |  |  |         type_definition: typing.Union[None, typing.Any, typing.Tuple[typing.Any]] = None, | 
					
						
							|  |  |  |         default: typing.Any = None, | 
					
						
							|  |  |  |         environ_name: str = None, | 
					
						
							|  |  |  |     ): | 
					
						
							| 
									
										
										
										
											2021-06-01 16:03:19 +02:00
										 |  |  |         self.type_definition = ( | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |             type_definition if type_definition is None or isinstance(type_definition, tuple) else (type_definition,) | 
					
						
							| 
									
										
										
										
											2021-06-01 16:03:19 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |         self.default = default | 
					
						
							|  |  |  |         self.environ_name = environ_name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def type_definition_repr(self): | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |         types_str = [t.__name__ if isinstance(t, type) else repr(t) for t in self.type_definition] | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |         return ', '.join(types_str) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def check_type_definition(self, value: typing.Any) -> None: | 
					
						
							|  |  |  |         if value in self.type_definition: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         type_list = tuple(t for t in self.type_definition if isinstance(t, type)) | 
					
						
							|  |  |  |         if not isinstance(value, type_list): | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |             raise ValueError('The value has to be one of these types/values: {}'.format(self.type_definition_repr)) | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __call__(self, value: typing.Any) -> typing.Any: | 
					
						
							|  |  |  |         if value == _UNDEFINED: | 
					
						
							|  |  |  |             value = self.default | 
					
						
							|  |  |  |         # override existing value with environ | 
					
						
							|  |  |  |         if self.environ_name and self.environ_name in os.environ: | 
					
						
							|  |  |  |             value = os.environ[self.environ_name] | 
					
						
							|  |  |  |             if self.type_definition == (bool,): | 
					
						
							|  |  |  |                 value = STR_TO_BOOL[value.lower()] | 
					
						
							| 
									
										
										
										
											2021-06-01 16:03:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |         self.check_type_definition(value) | 
					
						
							|  |  |  |         return value | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-21 10:41:57 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | class SettingSublistValue(SettingsValue): | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |     """Check the value is a sublist of type definition.""" | 
					
						
							| 
									
										
										
										
											2021-10-21 10:41:57 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def check_type_definition(self, value: typing.Any) -> typing.Any: | 
					
						
							|  |  |  |         if not isinstance(value, list): | 
					
						
							|  |  |  |             raise ValueError('The value has to a list') | 
					
						
							|  |  |  |         for item in value: | 
					
						
							|  |  |  |             if not item in self.type_definition[0]: | 
					
						
							|  |  |  |                 raise ValueError('{} not in {}'.format(item, self.type_definition)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  | class SettingsDirectoryValue(SettingsValue): | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |     """Check and update a setting value that is a directory path""" | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def check_type_definition(self, value: typing.Any) -> typing.Any: | 
					
						
							|  |  |  |         super().check_type_definition(value) | 
					
						
							|  |  |  |         if not os.path.isdir(value): | 
					
						
							|  |  |  |             raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __call__(self, value: typing.Any) -> typing.Any: | 
					
						
							|  |  |  |         if value == '': | 
					
						
							|  |  |  |             value = self.default | 
					
						
							|  |  |  |         return super().__call__(value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def apply_schema(settings, schema, path_list): | 
					
						
							|  |  |  |     error = False | 
					
						
							|  |  |  |     for key, value in schema.items(): | 
					
						
							|  |  |  |         if isinstance(value, SettingsValue): | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 settings[key] = value(settings.get(key, _UNDEFINED)) | 
					
						
							|  |  |  |             except Exception as e:  # pylint: disable=broad-except | 
					
						
							|  |  |  |                 # don't stop now: check other values | 
					
						
							|  |  |  |                 logger.error('%s: %s', '.'.join([*path_list, key]), e) | 
					
						
							|  |  |  |                 error = True | 
					
						
							|  |  |  |         elif isinstance(value, dict): | 
					
						
							|  |  |  |             error = error or apply_schema(settings.setdefault(key, {}), schema[key], [*path_list, key]) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             settings.setdefault(key, value) | 
					
						
							|  |  |  |     if len(path_list) == 0 and error: | 
					
						
							|  |  |  |         raise ValueError('Invalid settings.yml') | 
					
						
							|  |  |  |     return error | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SCHEMA = { | 
					
						
							|  |  |  |     'general': { | 
					
						
							| 
									
										
										
										
											2021-10-02 12:21:02 +02:00
										 |  |  |         'debug': SettingsValue(bool, False, 'SEARXNG_DEBUG'), | 
					
						
							|  |  |  |         'instance_name': SettingsValue(str, 'SearXNG'), | 
					
						
							| 
									
										
										
										
											2022-06-15 22:06:52 +02:00
										 |  |  |         'privacypolicy_url': SettingsValue((None, False, str), None), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |         'contact_url': SettingsValue((None, False, str), None), | 
					
						
							| 
									
										
										
										
											2021-12-26 22:44:46 +01:00
										 |  |  |         'enable_metrics': SettingsValue(bool, True), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |     }, | 
					
						
							|  |  |  |     'brand': { | 
					
						
							| 
									
										
										
										
											2021-10-21 12:11:42 +02:00
										 |  |  |         'issue_url': SettingsValue(str, 'https://github.com/searxng/searxng/issues'), | 
					
						
							|  |  |  |         'new_issue_url': SettingsValue(str, 'https://github.com/searxng/searxng/issues/new'), | 
					
						
							| 
									
										
										
										
											2022-01-02 20:54:41 +01:00
										 |  |  |         'docs_url': SettingsValue(str, 'https://docs.searxng.org'), | 
					
						
							| 
									
										
										
										
											2021-10-21 12:11:42 +02:00
										 |  |  |         'public_instances': SettingsValue(str, 'https://searx.space'), | 
					
						
							|  |  |  |         'wiki_url': SettingsValue(str, 'https://github.com/searxng/searxng/wiki'), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |     }, | 
					
						
							|  |  |  |     'search': { | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |         'safe_search': SettingsValue((0, 1, 2), 0), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |         'autocomplete': SettingsValue(str, ''), | 
					
						
							| 
									
										
										
										
											2022-04-22 13:47:54 +02:00
										 |  |  |         'autocomplete_min': SettingsValue(int, 4), | 
					
						
							| 
									
										
										
										
											2021-10-21 10:41:57 +02:00
										 |  |  |         'default_lang': SettingsValue(tuple(LANGUAGE_CODES + ['']), ''), | 
					
						
							|  |  |  |         'languages': SettingSublistValue(LANGUAGE_CODES, LANGUAGE_CODES), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |         'ban_time_on_fail': SettingsValue(numbers.Real, 5), | 
					
						
							|  |  |  |         'max_ban_time_on_fail': SettingsValue(numbers.Real, 120), | 
					
						
							|  |  |  |         'formats': SettingsValue(list, OUTPUT_FORMATS), | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     'server': { | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |         'port': SettingsValue((int, str), 8888, 'SEARXNG_PORT'), | 
					
						
							| 
									
										
										
										
											2021-10-02 12:21:02 +02:00
										 |  |  |         'bind_address': SettingsValue(str, '127.0.0.1', 'SEARXNG_BIND_ADDRESS'), | 
					
						
							| 
									
										
										
										
											2021-12-19 11:01:50 +01:00
										 |  |  |         'limiter': SettingsValue(bool, False), | 
					
						
							| 
									
										
										
										
											2021-10-02 12:21:02 +02:00
										 |  |  |         'secret_key': SettingsValue(str, environ_name='SEARXNG_SECRET'), | 
					
						
							| 
									
										
										
										
											2022-03-19 20:20:13 +01:00
										 |  |  |         'base_url': SettingsValue((False, str), False, 'SEARXNG_BASE_URL'), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |         'image_proxy': SettingsValue(bool, False), | 
					
						
							|  |  |  |         'http_protocol_version': SettingsValue(('1.0', '1.1'), '1.0'), | 
					
						
							|  |  |  |         'method': SettingsValue(('POST', 'GET'), 'POST'), | 
					
						
							|  |  |  |         'default_http_headers': SettingsValue(dict, {}), | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2022-01-03 17:43:20 +01:00
										 |  |  |     'redis': { | 
					
						
							|  |  |  |         'url': SettingsValue(str, 'unix:///usr/local/searxng-redis/run/redis.sock?db=0'), | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |     'ui': { | 
					
						
							|  |  |  |         'static_path': SettingsDirectoryValue(str, os.path.join(searx_dir, 'static')), | 
					
						
							| 
									
										
										
										
											2022-03-20 16:28:14 +01:00
										 |  |  |         'static_use_hash': SettingsValue(bool, False), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |         'templates_path': SettingsDirectoryValue(str, os.path.join(searx_dir, 'templates')), | 
					
						
							| 
									
										
										
										
											2021-12-29 14:49:06 +01:00
										 |  |  |         'default_theme': SettingsValue(str, 'simple'), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |         'default_locale': SettingsValue(str, ''), | 
					
						
							|  |  |  |         'theme_args': { | 
					
						
							| 
									
										
										
										
											2021-11-19 13:49:16 +01:00
										 |  |  |             'simple_style': SettingsValue(SIMPLE_STYLE, 'auto'), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |         }, | 
					
						
							| 
									
										
										
										
											2022-06-03 14:10:48 +02:00
										 |  |  |         'center_aligment': SettingsValue(bool, False), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |         'results_on_new_tab': SettingsValue(bool, False), | 
					
						
							|  |  |  |         'advanced_search': SettingsValue(bool, False), | 
					
						
							| 
									
										
										
										
											2021-11-06 12:26:48 +01:00
										 |  |  |         'query_in_title': SettingsValue(bool, False), | 
					
						
							| 
									
										
										
										
											2022-01-23 11:37:57 +01:00
										 |  |  |         'infinite_scroll': SettingsValue(bool, False), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |     }, | 
					
						
							|  |  |  |     'preferences': { | 
					
						
							|  |  |  |         'lock': SettingsValue(list, []), | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     'outgoing': { | 
					
						
							|  |  |  |         'useragent_suffix': SettingsValue(str, ''), | 
					
						
							|  |  |  |         'request_timeout': SettingsValue(numbers.Real, 3.0), | 
					
						
							|  |  |  |         'enable_http2': SettingsValue(bool, True), | 
					
						
							|  |  |  |         'max_request_timeout': SettingsValue((None, numbers.Real), None), | 
					
						
							|  |  |  |         # Magic number kept from previous code | 
					
						
							|  |  |  |         'pool_connections': SettingsValue(int, 100), | 
					
						
							|  |  |  |         # Picked from constructor | 
					
						
							|  |  |  |         'pool_maxsize': SettingsValue(int, 10), | 
					
						
							|  |  |  |         'keepalive_expiry': SettingsValue(numbers.Real, 5.0), | 
					
						
							|  |  |  |         # default maximum redirect | 
					
						
							|  |  |  |         # from https://github.com/psf/requests/blob/8c211a96cdbe9fe320d63d9e1ae15c5c07e179f8/requests/models.py#L55 | 
					
						
							|  |  |  |         'max_redirects': SettingsValue(int, 30), | 
					
						
							|  |  |  |         'retries': SettingsValue(int, 0), | 
					
						
							|  |  |  |         'proxies': SettingsValue((None, str, dict), None), | 
					
						
							|  |  |  |         'source_ips': SettingsValue((None, str, list), None), | 
					
						
							|  |  |  |         # Tor configuration | 
					
						
							|  |  |  |         'using_tor_proxy': SettingsValue(bool, False), | 
					
						
							|  |  |  |         'extra_proxy_timeout': SettingsValue(int, 0), | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |         'networks': {}, | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |     }, | 
					
						
							| 
									
										
										
										
											2021-09-13 19:37:51 +02:00
										 |  |  |     'plugins': SettingsValue(list, []), | 
					
						
							|  |  |  |     'enabled_plugins': SettingsValue((None, list), None), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |     'checker': { | 
					
						
							|  |  |  |         'off_when_debug': SettingsValue(bool, True), | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2022-01-04 18:00:45 +01:00
										 |  |  |     'categories_as_tabs': SettingsValue(dict, CATEGORIES_AS_TABS), | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |     'engines': SettingsValue(list, []), | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  |     'doi_resolvers': {}, | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-27 09:26:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  | def settings_set_defaults(settings): | 
					
						
							| 
									
										
										
										
											2021-10-02 12:21:02 +02:00
										 |  |  |     # compatibility with searx variables | 
					
						
							|  |  |  |     for searx, searxng in SEARX_ENVIRON_VARIABLES.items(): | 
					
						
							|  |  |  |         if searx in os.environ and searxng not in os.environ: | 
					
						
							|  |  |  |             os.environ[searxng] = os.environ[searx] | 
					
						
							|  |  |  |             logger.warning('%s uses value from %s', searxng, searx) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-28 18:45:22 +02:00
										 |  |  |     apply_schema(settings, SCHEMA, []) | 
					
						
							|  |  |  |     return settings |