1"""
2    sphinx.util.requests
3    ~~~~~~~~~~~~~~~~~~~~
4
5    Simple requests package loader
6
7    :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
8    :license: BSD, see LICENSE for details.
9"""
10
11import sys
12import warnings
13from contextlib import contextmanager
14from typing import Any, Generator, Union
15from urllib.parse import urlsplit
16
17import requests
18
19import sphinx
20from sphinx.config import Config
21from sphinx.deprecation import RemovedInSphinx50Warning
22
23try:
24    from requests.packages.urllib3.exceptions import SSLError
25except ImportError:
26    # python-requests package in Debian jessie does not provide ``requests.packages.urllib3``.
27    # So try to import the exceptions from urllib3 package.
28    from urllib3.exceptions import SSLError  # type: ignore
29
30try:
31    from requests.packages.urllib3.exceptions import InsecureRequestWarning
32except ImportError:
33    try:
34        # for Debian-jessie
35        from urllib3.exceptions import InsecureRequestWarning  # type: ignore
36    except ImportError:
37        # for requests < 2.4.0
38        InsecureRequestWarning = None  # type: ignore
39
40
41useragent_header = [('User-Agent',
42                     'Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0')]
43
44
45def is_ssl_error(exc: Exception) -> bool:
46    """Check an exception is SSLError."""
47    warnings.warn(
48        "is_ssl_error() is outdated and likely returns incorrect results "
49        "for modern versions of Requests.",
50        RemovedInSphinx50Warning)
51    if isinstance(exc, SSLError):
52        return True
53    else:
54        args = getattr(exc, 'args', [])
55        if args and isinstance(args[0], SSLError):
56            return True
57        else:
58            return False
59
60
61@contextmanager
62def ignore_insecure_warning(**kwargs: Any) -> Generator[None, None, None]:
63    with warnings.catch_warnings():
64        if not kwargs.get('verify') and InsecureRequestWarning:
65            # ignore InsecureRequestWarning if verify=False
66            warnings.filterwarnings("ignore", category=InsecureRequestWarning)
67        yield
68
69
70def _get_tls_cacert(url: str, config: Config) -> Union[str, bool]:
71    """Get additional CA cert for a specific URL.
72
73    This also returns ``False`` if verification is disabled.
74    And returns ``True`` if additional CA cert not found.
75    """
76    if not config.tls_verify:
77        return False
78
79    certs = getattr(config, 'tls_cacerts', None)
80    if not certs:
81        return True
82    elif isinstance(certs, (str, tuple)):
83        return certs  # type: ignore
84    else:
85        hostname = urlsplit(url)[1]
86        if '@' in hostname:
87            hostname = hostname.split('@')[1]
88
89        return certs.get(hostname, True)
90
91
92def _get_user_agent(config: Config) -> str:
93    if config.user_agent:
94        return config.user_agent
95    else:
96        return ' '.join([
97            'Sphinx/%s' % sphinx.__version__,
98            'requests/%s' % requests.__version__,
99            'python/%s' % '.'.join(map(str, sys.version_info[:3])),
100        ])
101
102
103def get(url: str, **kwargs: Any) -> requests.Response:
104    """Sends a GET request like requests.get().
105
106    This sets up User-Agent header and TLS verification automatically."""
107    headers = kwargs.setdefault('headers', {})
108    config = kwargs.pop('config', None)
109    if config:
110        kwargs.setdefault('verify', _get_tls_cacert(url, config))
111        headers.setdefault('User-Agent', _get_user_agent(config))
112    else:
113        headers.setdefault('User-Agent', useragent_header[0][1])
114
115    with ignore_insecure_warning(**kwargs):
116        return requests.get(url, **kwargs)
117
118
119def head(url: str, **kwargs: Any) -> requests.Response:
120    """Sends a HEAD request like requests.head().
121
122    This sets up User-Agent header and TLS verification automatically."""
123    headers = kwargs.setdefault('headers', {})
124    config = kwargs.pop('config', None)
125    if config:
126        kwargs.setdefault('verify', _get_tls_cacert(url, config))
127        headers.setdefault('User-Agent', _get_user_agent(config))
128    else:
129        headers.setdefault('User-Agent', useragent_header[0][1])
130
131    with ignore_insecure_warning(**kwargs):
132        return requests.head(url, **kwargs)
133