diff --git a/searx/preferences.py b/searx/preferences.py index 3da6d5d16..8552305a7 100644 --- a/searx/preferences.py +++ b/searx/preferences.py @@ -8,9 +8,10 @@ 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 +from typing import Iterable, Dict, List, Optional import flask +import babel from searx import settings, autocomplete from searx.enginelib import Engine @@ -287,10 +288,65 @@ class PluginsSetting(BooleanChoices): return [item[len('plugin_') :] for item in items] +class ClientPref: + """Container to assemble client prefferences and settings.""" + + # hint: searx.webapp.get_client_settings should be moved into this class + + locale: babel.Locale + """Locale prefered by the client.""" + + def __init__(self, locale: Optional[babel.Locale] = None): + self.locale = locale + + @property + def locale_tag(self): + if self.locale is None: + return None + tag = self.locale.language + if self.locale.territory: + tag += '-' + self.locale.territory + return tag + + @classmethod + def from_http_request(cls, http_request: flask.Request): + """Build ClientPref object from HTTP request. + + - `Accept-Language used for locale setting + `__ + + """ + al_header = http_request.headers.get("Accept-Language") + if not al_header: + return cls(locale=None) + + pairs = [] + for l in al_header.split(','): + # fmt: off + lang, qvalue = [_.strip() for _ in (l.split(';') + ['q=1',])[:2]] + # fmt: on + try: + qvalue = float(qvalue.split('=')[-1]) + locale = babel.Locale.parse(lang, sep='-') + except (ValueError, babel.core.UnknownLocaleError): + continue + pairs.append((locale, qvalue)) + pairs.sort(reverse=True, key=lambda x: x[1]) + return cls(locale=pairs[0][0]) + + class Preferences: """Validates and saves preferences to cookies""" - def __init__(self, themes: List[str], categories: List[str], engines: Dict[str, Engine], plugins: Iterable[Plugin]): + def __init__( + self, + themes: List[str], + categories: List[str], + engines: Dict[str, Engine], + plugins: Iterable[Plugin], + client: Optional[ClientPref] = None, + ): + super().__init__() self.key_value_settings: Dict[str, Setting] = { @@ -414,6 +470,7 @@ class Preferences: self.engines = EnginesSetting('engines', engines=engines.values()) self.plugins = PluginsSetting('plugins', plugins=plugins) self.tokens = SetSetting('tokens') + self.client = client or ClientPref() self.unknown_params: Dict[str, str] = {} def get_as_url_params(self): diff --git a/searx/webapp.py b/searx/webapp.py index 388b28c38..52b1ccc34 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -96,6 +96,7 @@ from searx.plugins import Plugin, plugins, initialize as plugin_initialize from searx.plugins.oa_doi_rewrite import get_doi_resolver from searx.preferences import ( Preferences, + ClientPref, ValidationException, ) from searx.answerers import ( @@ -221,16 +222,9 @@ babel = Babel(app, locale_selector=get_locale) def _get_browser_language(req, lang_list): - for lang in req.headers.get("Accept-Language", "en").split(","): - if ';' in lang: - lang = lang.split(';')[0] - if '-' in lang: - lang_parts = lang.split('-') - lang = "{}-{}".format(lang_parts[0], lang_parts[-1].upper()) - locale = match_locale(lang, lang_list, fallback=None) - if locale is not None: - return locale - return 'en' + client = ClientPref.from_http_request(req) + locale = match_locale(client.locale_tag, lang_list, fallback='en') + return locale def _get_locale_rfc5646(locale): @@ -512,7 +506,10 @@ def pre_request(): request.timings = [] # pylint: disable=assigning-non-slot request.errors = [] # pylint: disable=assigning-non-slot - preferences = Preferences(themes, list(categories.keys()), engines, plugins) # pylint: disable=redefined-outer-name + client_pref = ClientPref.from_http_request(request) + # pylint: disable=redefined-outer-name + preferences = Preferences(themes, list(categories.keys()), engines, plugins, client_pref) + user_agent = request.headers.get('User-Agent', '').lower() if 'webkit' in user_agent and 'android' in user_agent: preferences.key_value_settings['method'].value = 'GET'