From ddc0fbf5c9462a3a8df6891cb8b3d04a8a60f553 Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 10:25:19 +0100 Subject: [PATCH 01/13] custom: Add settings for logo and favicon --- searx/settings.yml | 2 ++ searx/settings_defaults.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/searx/settings.yml b/searx/settings.yml index 841457b5e..3fc2ab60b 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -14,6 +14,8 @@ general: enable_metrics: true brand: + favicon: static/favicon.ico + #logo: searx/static/themes/simple/img/searxng.png new_issue_url: https://github.com/searxng/searxng/issues/new docs_url: https://docs.searxng.org/ public_instances: https://searx.space diff --git a/searx/settings_defaults.py b/searx/settings_defaults.py index 6e98076ff..936ae5da6 100644 --- a/searx/settings_defaults.py +++ b/searx/settings_defaults.py @@ -146,6 +146,8 @@ SCHEMA = { 'enable_metrics': SettingsValue(bool, True), }, 'brand': { + 'favicon': SettingsValue(str, "static/themes/simple/img/favicon.png"), + 'logo': SettingsValue(str, "static/themes/simple/img/searxg.png"), 'issue_url': SettingsValue(str, 'https://github.com/searxng/searxng/issues'), 'new_issue_url': SettingsValue(str, 'https://github.com/searxng/searxng/issues/new'), 'docs_url': SettingsValue(str, 'https://docs.searxng.org'), From 0b600740a5bacecb9d3d884795486cf8a6bca894 Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 10:25:50 +0100 Subject: [PATCH 02/13] Rewrite /favicon.ico to respect settings and add /logo --- searx/webapp.py | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/searx/webapp.py b/searx/webapp.py index 95c33f704..44e0139a2 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -298,6 +298,31 @@ def get_result_template(theme_name: str, template_name: str): return themed_path return 'result_templates/' + template_name +def get_favicon_or_logo(imgtype: str, request): + # This method returns a favicon or regular image depending on the imgtype parameter + # It is used to not repeat code for /favicon.ico and /logo + theme = request.preferences.get_value("theme") + path = os.path.expanduser(settings.get("brand").get(imgtype)) + relpath = os.path.join(app.root_path, path) + mimetype = 'image/vnd.microsoft.icon' if imgtype == "favicon" else None + + if not os.path.isfile(path) and not os.path.isfile(relpath): + # If path doesn't exist neither relatively nor absolutely, fallback to whatever is in the theme + return send_from_directory( + os.path.join(app.root_path, settings['ui']['static_path'], 'themes', theme, 'img'), # pyright: ignore + 'favicon.png' if imgtype == "favicon" else "searxng.png", + mimetype=mimetype, + ) + else: + # Otherwise we're good to go, send whatever is specified. + filename = os.path.basename(path) + path = os.path.dirname(path) + # Works wih both relative and absolute. + return send_from_directory( + path, + filename, + mimetype=mimetype + ) def custom_url_for(endpoint: str, **values): suffix = "" @@ -1303,12 +1328,12 @@ def opensearch(): @app.route('/favicon.ico') def favicon(): - theme = request.preferences.get_value("theme") - return send_from_directory( - os.path.join(app.root_path, settings['ui']['static_path'], 'themes', theme, 'img'), # pyright: ignore - 'favicon.png', - mimetype='image/vnd.microsoft.icon', - ) + return get_favicon_or_logo("favicon", request) + +@app.route('/logo') +def logo(): + return get_favicon_or_logo("logo", request) + @app.route('/clear_cookies') From 78dfe4307cb4f1c06cc0bc66e4d1e2bca64e57cb Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 10:26:12 +0100 Subject: [PATCH 03/13] modify base.html to respect favicon settings --- searx/templates/simple/base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/searx/templates/simple/base.html b/searx/templates/simple/base.html index a31ff07ee..b53987db4 100644 --- a/searx/templates/simple/base.html +++ b/searx/templates/simple/base.html @@ -24,8 +24,8 @@ {% block head %} {% endblock %} - - + +
From 366b26564ef3457a5439e84cb0b40627f13d31cf Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 10:30:37 +0100 Subject: [PATCH 04/13] modify page_with_header.html to respect settings --- searx/templates/simple/page_with_header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searx/templates/simple/page_with_header.html b/searx/templates/simple/page_with_header.html index d4466b612..a636092ec 100644 --- a/searx/templates/simple/page_with_header.html +++ b/searx/templates/simple/page_with_header.html @@ -1,5 +1,5 @@ {% set body_class = "page_with_header" %} {% extends "simple/base.html" %} {% block header %} - + {% endblock %} From 41e24ca0e1e05869c42e1d9a54b320db260daff3 Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 10:33:30 +0100 Subject: [PATCH 05/13] Remove test values in settings.yml --- searx/settings.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/searx/settings.yml b/searx/settings.yml index 3fc2ab60b..aaa44d7d9 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -14,8 +14,8 @@ general: enable_metrics: true brand: - favicon: static/favicon.ico - #logo: searx/static/themes/simple/img/searxng.png + favicon: static/themes/simple/img/favicon.png + logo: static/themes/simple/img/searxng.png new_issue_url: https://github.com/searxng/searxng/issues/new docs_url: https://docs.searxng.org/ public_instances: https://searx.space From 214386d2c8fac2b8abf31b183c824d59303fc2af Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 10:34:57 +0100 Subject: [PATCH 06/13] Add settings.yml comment --- searx/settings.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/searx/settings.yml b/searx/settings.yml index aaa44d7d9..aa543c194 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -14,6 +14,7 @@ general: enable_metrics: true brand: + # Both favicon and logo are relative to SearXNG's root path unless they are absolute paths favicon: static/themes/simple/img/favicon.png logo: static/themes/simple/img/searxng.png new_issue_url: https://github.com/searxng/searxng/issues/new From 3802f281dd63d08c1ddf207671953ee0b8106088 Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 10:54:19 +0100 Subject: [PATCH 07/13] doc: Add and fix comment in get_favicon_or_logo() --- searx/webapp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/searx/webapp.py b/searx/webapp.py index 44e0139a2..60978266b 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -301,6 +301,7 @@ def get_result_template(theme_name: str, template_name: str): def get_favicon_or_logo(imgtype: str, request): # This method returns a favicon or regular image depending on the imgtype parameter # It is used to not repeat code for /favicon.ico and /logo + # Called by logo() and favicon() theme = request.preferences.get_value("theme") path = os.path.expanduser(settings.get("brand").get(imgtype)) relpath = os.path.join(app.root_path, path) @@ -317,7 +318,7 @@ def get_favicon_or_logo(imgtype: str, request): # Otherwise we're good to go, send whatever is specified. filename = os.path.basename(path) path = os.path.dirname(path) - # Works wih both relative and absolute. + # Works with both relative and absolute. return send_from_directory( path, filename, From 0dcf64cf8ecc663e65c1113d54d69f9754d6e207 Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 11:02:02 +0100 Subject: [PATCH 08/13] Improve readability in get_favicon_or_logo() --- searx/webapp.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/searx/webapp.py b/searx/webapp.py index 60978266b..fe80602ae 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -298,6 +298,7 @@ def get_result_template(theme_name: str, template_name: str): return themed_path return 'result_templates/' + template_name + def get_favicon_or_logo(imgtype: str, request): # This method returns a favicon or regular image depending on the imgtype parameter # It is used to not repeat code for /favicon.ico and /logo @@ -316,12 +317,10 @@ def get_favicon_or_logo(imgtype: str, request): ) else: # Otherwise we're good to go, send whatever is specified. - filename = os.path.basename(path) - path = os.path.dirname(path) # Works with both relative and absolute. return send_from_directory( - path, - filename, + os.path.dirname(path), + os.path.basename(path), mimetype=mimetype ) From 54993ad2fa0c4591ec4f30841772264044d9c8a7 Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 21:41:32 +0100 Subject: [PATCH 09/13] Add support for pulling favicon and logo from a URL --- searx/webapp.py | 62 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/searx/webapp.py b/searx/webapp.py index fe80602ae..a4250f709 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -307,14 +307,64 @@ def get_favicon_or_logo(imgtype: str, request): path = os.path.expanduser(settings.get("brand").get(imgtype)) relpath = os.path.join(app.root_path, path) mimetype = 'image/vnd.microsoft.icon' if imgtype == "favicon" else None - - if not os.path.isfile(path) and not os.path.isfile(relpath): - # If path doesn't exist neither relatively nor absolutely, fallback to whatever is in the theme - return send_from_directory( + fallback = ( send_from_directory( os.path.join(app.root_path, settings['ui']['static_path'], 'themes', theme, 'img'), # pyright: ignore 'favicon.png' if imgtype == "favicon" else "searxng.png", - mimetype=mimetype, - ) + mimetype=mimetype + ) ) + + # If path is a URL + if "://" in path: + resp_ok = False + resp = None + # TODO: Make a a function for this used by both this function and the image proxy + try: + # Pull image from it + request_headers = { + 'User-Agent': gen_useragent(), + 'Accept': 'image/webp,*/*', + 'Accept-Encoding': 'gzip, deflate', + 'Sec-GPC': '1', + 'DNT': '1', + } + resp, stream = http_stream(method='GET', url=path, headers=request_headers, allow_redirects=True) + if resp.status_code != 200: + logger.debug(f"{imgtype}: Bad status code %i", resp.status_code) + if resp.status_code >= 400: + return fallback + resp_ok = True + except httpx.HTTPError: + logger.debug(f"{imgtype}: HTTP error") + return fallback + finally: + if resp and not resp_ok: + try: + resp.close() + except httpx.HTTPError: + logger.exception(f'{imgtype}: HTTP error on closing') + def close_stream(): + nonlocal resp, stream + try: + if resp: + resp.close() + del resp + del stream + except httpx.HTTPError as e: + logger.debug(f'{imgtype}Exception while closing response', e) + try: + headers = dict_subset(resp.headers, {'Content-Type', 'Content-Encoding', 'Content-Length', 'Length'}) + response = Response(stream, mimetype=resp.headers['Content-Type'], headers=headers, direct_passthrough=True) + response.call_on_close(close_stream) + return response + except httpx.HTTPError: + close_stream() + return fallback + + + + elif not os.path.isfile(path) and not os.path.isfile(relpath): + # If path doesn't exist neither relatively nor absolutely, fallback to whatever is in the theme + return fallback else: # Otherwise we're good to go, send whatever is specified. # Works with both relative and absolute. From d5ce6a27bb3a222b60e582216c8af0bc8f81dd16 Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 22:03:51 +0100 Subject: [PATCH 10/13] Pass tests --- searx/webapp.py | 153 ++++++++++++++++++++++++------------------------ 1 file changed, 76 insertions(+), 77 deletions(-) diff --git a/searx/webapp.py b/searx/webapp.py index a4250f709..2a1ba6c48 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -299,80 +299,79 @@ def get_result_template(theme_name: str, template_name: str): return 'result_templates/' + template_name -def get_favicon_or_logo(imgtype: str, request): - # This method returns a favicon or regular image depending on the imgtype parameter - # It is used to not repeat code for /favicon.ico and /logo - # Called by logo() and favicon() - theme = request.preferences.get_value("theme") - path = os.path.expanduser(settings.get("brand").get(imgtype)) - relpath = os.path.join(app.root_path, path) - mimetype = 'image/vnd.microsoft.icon' if imgtype == "favicon" else None - fallback = ( send_from_directory( - os.path.join(app.root_path, settings['ui']['static_path'], 'themes', theme, 'img'), # pyright: ignore - 'favicon.png' if imgtype == "favicon" else "searxng.png", - mimetype=mimetype - ) ) - - # If path is a URL - if "://" in path: - resp_ok = False - resp = None - # TODO: Make a a function for this used by both this function and the image proxy - try: - # Pull image from it - request_headers = { - 'User-Agent': gen_useragent(), - 'Accept': 'image/webp,*/*', - 'Accept-Encoding': 'gzip, deflate', - 'Sec-GPC': '1', - 'DNT': '1', - } - resp, stream = http_stream(method='GET', url=path, headers=request_headers, allow_redirects=True) - if resp.status_code != 200: - logger.debug(f"{imgtype}: Bad status code %i", resp.status_code) - if resp.status_code >= 400: - return fallback - resp_ok = True - except httpx.HTTPError: - logger.debug(f"{imgtype}: HTTP error") - return fallback - finally: - if resp and not resp_ok: - try: - resp.close() - except httpx.HTTPError: - logger.exception(f'{imgtype}: HTTP error on closing') - def close_stream(): - nonlocal resp, stream - try: - if resp: - resp.close() - del resp - del stream - except httpx.HTTPError as e: - logger.debug(f'{imgtype}Exception while closing response', e) - try: - headers = dict_subset(resp.headers, {'Content-Type', 'Content-Encoding', 'Content-Length', 'Length'}) - response = Response(stream, mimetype=resp.headers['Content-Type'], headers=headers, direct_passthrough=True) - response.call_on_close(close_stream) - return response - except httpx.HTTPError: - close_stream() - return fallback +def get_favicon_or_logo(imgtype: str): + # This method returns a favicon or regular image depending on the imgtype parameter + # It is used to not repeat code for /favicon.ico and /logo + # Called by logo() and favicon() + theme = request.preferences.get_value("theme") + path = os.path.expanduser(settings.get("brand").get(imgtype)) + relpath = os.path.join(app.root_path, path) + mimetype = 'image/vnd.microsoft.icon' if imgtype == "favicon" else None + fallback = send_from_directory( + os.path.join(app.root_path, settings['ui']['static_path'], 'themes', theme, 'img'), # pyright: ignore + 'favicon.png' if imgtype == "favicon" else "searxng.png", + mimetype=mimetype, + ) + # If path is a URL + if "://" in path: + resp_ok = False + resp = None + # TODO: Make a a function for this used by both this function and the image proxy + # ASAP to avoid repeated code like this + # pylint: disable=fixme + try: + # Pull image from it + request_headers = { + 'User-Agent': gen_useragent(), + 'Accept': 'image/webp,*/*', + 'Accept-Encoding': 'gzip, deflate', + 'Sec-GPC': '1', + 'DNT': '1', + } + resp, stream = http_stream(method='GET', url=path, headers=request_headers, allow_redirects=True) + if resp.status_code != 200: + logger.debug(f"{imgtype}: Bad status code %i", resp.status_code) + if resp.status_code >= 400: + return fallback + resp_ok = True + except httpx.HTTPError: + logger.debug(f"{imgtype}: HTTP error") + return fallback + finally: + if resp and not resp_ok: + try: + resp.close() + except httpx.HTTPError: + logger.exception(f'{imgtype}: HTTP error on closing') + + def close_stream(): + nonlocal resp, stream + try: + if resp: + resp.close() + del resp + del stream + except httpx.HTTPError as e: + logger.debug(f'{imgtype}Exception while closing response', e) + + try: + headers = dict_subset(resp.headers, {'Content-Type', 'Content-Encoding', 'Content-Length', 'Length'}) + response = Response(stream, mimetype=resp.headers['Content-Type'], headers=headers, direct_passthrough=True) + response.call_on_close(close_stream) + return response + except httpx.HTTPError: + close_stream() + return fallback + + elif not os.path.isfile(path) and not os.path.isfile(relpath): + # If path doesn't exist neither relatively nor absolutely, fallback to whatever is in the theme + return fallback + else: + # Otherwise we're good to go, send whatever is specified. + # Works with both relative and absolute. + return send_from_directory(os.path.dirname(path), os.path.basename(path), mimetype=mimetype) - - elif not os.path.isfile(path) and not os.path.isfile(relpath): - # If path doesn't exist neither relatively nor absolutely, fallback to whatever is in the theme - return fallback - else: - # Otherwise we're good to go, send whatever is specified. - # Works with both relative and absolute. - return send_from_directory( - os.path.dirname(path), - os.path.basename(path), - mimetype=mimetype - ) def custom_url_for(endpoint: str, **values): suffix = "" @@ -1378,12 +1377,12 @@ def opensearch(): @app.route('/favicon.ico') def favicon(): - return get_favicon_or_logo("favicon", request) - + return get_favicon_or_logo("favicon", request) + + @app.route('/logo') -def logo(): - return get_favicon_or_logo("logo", request) - +def logo(): + return get_favicon_or_logo("logo", request) @app.route('/clear_cookies') From 659c00f2ca4c27cc1cd213ea8b9ef62decc8a20e Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 22:27:25 +0100 Subject: [PATCH 11/13] Truly pass test --- searx/webapp.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/searx/webapp.py b/searx/webapp.py index 2a1ba6c48..81b0a6d5c 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -317,9 +317,9 @@ def get_favicon_or_logo(imgtype: str): if "://" in path: resp_ok = False resp = None - # TODO: Make a a function for this used by both this function and the image proxy + # TODO: Make a a function for this used by both this function and the image proxy # pylint: disable=fixme # ASAP to avoid repeated code like this - # pylint: disable=fixme + try: # Pull image from it request_headers = { @@ -1377,12 +1377,12 @@ def opensearch(): @app.route('/favicon.ico') def favicon(): - return get_favicon_or_logo("favicon", request) + return get_favicon_or_logo("favicon") @app.route('/logo') def logo(): - return get_favicon_or_logo("logo", request) + return get_favicon_or_logo("logo") @app.route('/clear_cookies') From ac0fed69e49885da6238eac8bcf8195b16ec6a15 Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 22:39:31 +0100 Subject: [PATCH 12/13] Should always fallback if status code isnt 200 --- searx/webapp.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/searx/webapp.py b/searx/webapp.py index 81b0a6d5c..b1d82d927 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -332,8 +332,7 @@ def get_favicon_or_logo(imgtype: str): resp, stream = http_stream(method='GET', url=path, headers=request_headers, allow_redirects=True) if resp.status_code != 200: logger.debug(f"{imgtype}: Bad status code %i", resp.status_code) - if resp.status_code >= 400: - return fallback + return fallback resp_ok = True except httpx.HTTPError: logger.debug(f"{imgtype}: HTTP error") From 170a851024a7d553a0b910f7edd92886c941ca3e Mon Sep 17 00:00:00 2001 From: Solirs Date: Wed, 22 Mar 2023 22:40:56 +0100 Subject: [PATCH 13/13] log: Fix formatting --- searx/webapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searx/webapp.py b/searx/webapp.py index b1d82d927..b05ebe38a 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -352,7 +352,7 @@ def get_favicon_or_logo(imgtype: str): del resp del stream except httpx.HTTPError as e: - logger.debug(f'{imgtype}Exception while closing response', e) + logger.debug(f'{imgtype}: Exception while closing response', e) try: headers = dict_subset(resp.headers, {'Content-Type', 'Content-Encoding', 'Content-Length', 'Length'})