.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
http_sec_fetch.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2"""
3Method ``http_sec_fetch``
4-------------------------
5
6The ``http_sec_fetch`` method protect resources from web attacks with `Fetch
7Metadata`_. A request is filtered out in case of:
8
9- http header Sec-Fetch-Mode_ is invalid
10- http header Sec-Fetch-Dest_ is invalid
11
12.. _Fetch Metadata:
13 https://developer.mozilla.org/en-US/docs/Glossary/Fetch_metadata_request_header
14
15.. _Sec-Fetch-Dest:
16 https://developer.mozilla.org/en-US/docs/Web/API/Request/destination
17
18.. _Sec-Fetch-Mode:
19 https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
20
21
22"""
23# pylint: disable=unused-argument
24
25from __future__ import annotations
26from ipaddress import (
27 IPv4Network,
28 IPv6Network,
29)
30
31import re
32import flask
33import werkzeug
34
35from searx.extended_types import SXNG_Request
36
37from . import config
38from ._helpers import logger
39
40
41def is_browser_supported(user_agent: str) -> bool:
42 """Check if the browser supports Sec-Fetch headers.
43
44 https://caniuse.com/mdn-http_headers_sec-fetch-dest
45 https://caniuse.com/mdn-http_headers_sec-fetch-mode
46 https://caniuse.com/mdn-http_headers_sec-fetch-site
47
48 Supported browsers:
49 - Chrome >= 80
50 - Firefox >= 90
51 - Safari >= 16.4
52 - Edge (mirrors Chrome)
53 - Opera (mirrors Chrome)
54 """
55 user_agent = user_agent.lower()
56
57 # Chrome/Chromium/Edge/Opera
58 chrome_match = re.search(r'chrome/(\d+)', user_agent)
59 if chrome_match:
60 version = int(chrome_match.group(1))
61 return version >= 80
62
63 # Firefox
64 firefox_match = re.search(r'firefox/(\d+)', user_agent)
65 if firefox_match:
66 version = int(firefox_match.group(1))
67 return version >= 90
68
69 # Safari
70 safari_match = re.search(r'version/(\d+)\.(\d+)', user_agent)
71 if safari_match:
72 major = int(safari_match.group(1))
73 minor = int(safari_match.group(2))
74 return major > 16 or (major == 16 and minor >= 4)
75
76 return False
77
78
80 network: IPv4Network | IPv6Network,
81 request: SXNG_Request,
82 cfg: config.Config,
83) -> werkzeug.Response | None:
84
85 # Only check Sec-Fetch headers for supported browsers
86 user_agent = request.headers.get('User-Agent', '')
87 if is_browser_supported(user_agent):
88 val = request.headers.get("Sec-Fetch-Mode", "")
89 if val not in ('navigate', 'cors'):
90 logger.debug("invalid Sec-Fetch-Mode '%s'", val)
91 return flask.redirect(flask.url_for('index'), code=302)
92
93 val = request.headers.get("Sec-Fetch-Site", "")
94 if val not in ('same-origin', 'same-site', 'none'):
95 logger.debug("invalid Sec-Fetch-Site '%s'", val)
96 flask.redirect(flask.url_for('index'), code=302)
97
98 val = request.headers.get("Sec-Fetch-Dest", "")
99 if val not in ('document', 'empty'):
100 logger.debug("invalid Sec-Fetch-Dest '%s'", val)
101 flask.redirect(flask.url_for('index'), code=302)
102
103 return None
werkzeug.Response|None filter_request(IPv4Network|IPv6Network network, SXNG_Request request, config.Config cfg)
bool is_browser_supported(str user_agent)