"""Simple requests package loader"""
from __future__ import annotations
import warnings
from typing import TYPE_CHECKING
from urllib.parse import urljoin, urlsplit
import requests
from urllib3.exceptions import InsecureRequestWarning
import sphinx
if TYPE_CHECKING:
import re
from collections.abc import Sequence
from typing import Any
_USER_AGENT = (
f'Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0 '
f'Sphinx/{sphinx.__version__}'
)
class _IgnoredRedirection(Exception):
"""Sphinx-internal exception raised when an HTTP redirect is ignored"""
def __init__(self, destination: str, status_code: int) -> None:
self.destination = destination
self.status_code = status_code
def _get_tls_cacert(url: str, certs: str | dict[str, str] | None) -> str | bool:
"""Get additional CA cert for a specific URL."""
if not certs:
return True
elif isinstance(certs, (str, tuple)):
return certs
else:
hostname = urlsplit(url).netloc
if '@' in hostname:
hostname = hostname.partition('@')[-1]
return certs.get(hostname, True)
def get(url: str, **kwargs: Any) -> requests.Response:
"""Sends a GET request like ``requests.get()``.
This sets up User-Agent header and TLS verification automatically.
"""
with _Session() as session:
return session.get(url, **kwargs)
def head(url: str, **kwargs: Any) -> requests.Response:
"""Sends a HEAD request like ``requests.head()``.
This sets up User-Agent header and TLS verification automatically.
"""
with _Session() as session:
return session.head(url, **kwargs)
class _Session(requests.Session):
_ignored_redirects: Sequence[re.Pattern[str]]
def __init__(self, *args: Any, **kwargs: Any) -> None:
self._ignored_redirects = kwargs.pop('_ignored_redirects', ())
super().__init__(*args, **kwargs)
def get_redirect_target(self, resp: requests.Response) -> str | None:
"""Overrides the default requests.Session.get_redirect_target"""
# do not follow redirections that match ignored URI patterns
if resp.is_redirect:
destination = urljoin(resp.url, resp.headers['location'])
if any(pat.match(destination) for pat in self._ignored_redirects):
raise _IgnoredRedirection(
destination=destination, status_code=resp.status_code
)
return super().get_redirect_target(resp)
def request( # type: ignore[override]
self,
method: str,
url: str,
_user_agent: str = '',
_tls_info: tuple[bool, str | dict[str, str] | None] = (), # type: ignore[assignment]
**kwargs: Any,
) -> requests.Response:
"""Sends a request with an HTTP verb and url.
This sets up User-Agent header and TLS verification automatically.
"""
headers = kwargs.setdefault('headers', {})
headers.setdefault('User-Agent', _user_agent or _USER_AGENT)
if _tls_info:
tls_verify, tls_cacerts = _tls_info
verify = bool(kwargs.get('verify', tls_verify))
kwargs.setdefault('verify', verify and _get_tls_cacert(url, tls_cacerts))
else:
verify = kwargs.get('verify', True)
if verify:
return super().request(method, url, **kwargs)
with warnings.catch_warnings():
# ignore InsecureRequestWarning if verify=False
warnings.filterwarnings('ignore', category=InsecureRequestWarning)
return super().request(method, url, **kwargs)