45 """Configuration of the favicon proxy."""
47 max_age: int = 60 * 60 * 24 * 7
48 """HTTP header Cache-Control_ ``max-age``
50 .. _Cache-Control: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
53 secret_key: str = get_setting(
"server.secret_key")
54 """By default, the value from :ref:`server.secret_key <settings server>`
57 resolver_timeout: int = get_setting(
"outgoing.request_timeout")
58 """Timeout which the resolvers should not exceed, is usually passed to the
59 outgoing request of the resolver. By default, the value from
60 :ref:`outgoing.request_timeout <settings outgoing>` setting is used."""
62 resolver_map: dict[str, str] = msgspec.field(default_factory=_initial_resolver_map)
63 """The resolver_map is a key / value dictionary where the key is the name of
64 the resolver and the value is the fully qualifying name (fqn) of resolver's
65 function (the callable). The resolvers from the python module
66 :py:obj:`searx.favicons.resolver` are available by default."""
69 """Returns the callable object (function) of the resolver with the
70 ``name``. If no resolver is registered for the ``name``, ``None`` is
76 mod_name, _, func_name = fqn.rpartition(
'.')
77 mod = importlib.import_module(mod_name)
78 func = getattr(mod, func_name)
80 raise ValueError(f
"resolver {fqn} is not implemented")
83 favicon_path: str = get_setting(
"ui.static_path") +
"/themes/{theme}/img/empty_favicon.svg"
84 favicon_mime_type: str =
"image/svg+xml"
87 """Returns pathname and mimetype of the default favicon."""
94 """Returns data image URL of the default favicon."""
96 cache_key =
", ".join(f
"{x}:{replacements[x]}" for x
in sorted(list(replacements.keys()), key=str))
97 data_url = DEFAULT_FAVICON_URL.get(cache_key)
98 if data_url
is not None:
101 fav, mimetype = CFG.favicon(**replacements)
103 with fav.open(
"r", encoding=
"utf-8")
as f:
106 data_url = urllib.parse.quote(data_url)
107 data_url = f
"data:{mimetype};utf8,{data_url}"
108 DEFAULT_FAVICON_URL[cache_key] = data_url
113 """REST API of SearXNG's favicon proxy service
117 /favicon_proxy?authority=<...>&h=<...>
120 Domain name :rfc:`3986` / see :py:obj:`favicon_url`
123 HMAC :rfc:`2104`, build up from the :ref:`server.secret_key <settings
127 authority = flask.request.args.get(
'authority')
130 if not authority
or "/" in authority:
137 flask.request.args.get(
'h',
''),
141 resolver = flask.request.preferences.get_value(
'favicon_resolver')
143 if not resolver
or resolver
not in CFG.resolver_map.keys():
148 if data
is not None and mime
is not None:
149 resp = flask.Response(data, mimetype=mime)
150 resp.headers[
'Cache-Control'] = f
"max-age={CFG.max_age}"
154 theme = flask.request.preferences.get_value(
"theme")
155 fav, mimetype = CFG.favicon(theme=theme)
156 return flask.send_from_directory(fav.parent, fav.name, mimetype=mimetype)
159def search_favicon(resolver: str, authority: str) -> tuple[
None | bytes,
None | str]:
160 """Sends the request to the favicon resolver and returns a tuple for the
161 favicon. The tuple consists of ``(data, mime)``, if the resolver has not
162 determined a favicon, both values are ``None``.
165 Binary data of the favicon.
168 Mime type of the favicon.
172 data, mime = (
None,
None)
174 func = CFG.get_resolver(resolver)
179 data_mime = cache.CACHE(resolver, authority)
180 if data_mime
is not None:
184 data, mime = func(authority, timeout=CFG.resolver_timeout)
185 if data
is None or mime
is None:
186 data, mime = (
None,
None)
188 except (HTTPError, SearxEngineResponseException):
191 cache.CACHE.set(resolver, authority, mime, data)
196 """Function to generate the image URL used for favicons in SearXNG's result
197 lists. The ``authority`` argument (aka netloc / :rfc:`3986`) is usually a
198 (sub-) domain name. This function is used in the HTML (jinja) templates.
202 <div class="favicon">
203 <img src="{{ favicon_url(result.parsed_url.netloc) }}">
206 The returned URL is a route to :py:obj:`favicon_proxy` REST API.
208 If the favicon is already in the cache, the returned URL is a `data URL`_
209 (something like ``data:image/png;base64,...``). By generating a data url from
210 the :py:obj:`.cache.FaviconCache`, additional HTTP roundtripps via the
211 :py:obj:`favicon_proxy` are saved. However, it must also be borne in mind
212 that data urls are not cached in the client (web browser).
214 .. _data URL: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs
218 resolver = flask.request.preferences.get_value(
'favicon_resolver')
220 if not resolver
or resolver
not in CFG.resolver_map.keys():
223 data_mime = cache.CACHE(resolver, authority)
225 if data_mime == (
None,
None):
227 theme = flask.request.preferences.get_value(
"theme")
228 return CFG.favicon_data_url(theme=theme)
230 if data_mime
is not None:
231 data, mime = data_mime
232 return f
"data:{mime};base64,{str(base64.b64encode(data), 'utf-8')}"
234 h = new_hmac(CFG.secret_key, authority.encode())
235 proxy_url = flask.url_for(
'favicon_proxy')
236 query = urllib.parse.urlencode({
"authority": authority,
"h": h})
237 return f
"{proxy_url}?{query}"