annotate urllib3/contrib/socks.py @ 16:dc2c003078e9 tip

planemo upload for repository https://toolrepo.galaxytrakr.org/view/jpayne/bioproject_to_srr_2/556cac4fb538
author jpayne
date Tue, 21 May 2024 01:09:25 -0400
parents 5eb2d5e3bf22
children
rev   line source
jpayne@7 1 """
jpayne@7 2 This module contains provisional support for SOCKS proxies from within
jpayne@7 3 urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and
jpayne@7 4 SOCKS5. To enable its functionality, either install PySocks or install this
jpayne@7 5 module with the ``socks`` extra.
jpayne@7 6
jpayne@7 7 The SOCKS implementation supports the full range of urllib3 features. It also
jpayne@7 8 supports the following SOCKS features:
jpayne@7 9
jpayne@7 10 - SOCKS4A (``proxy_url='socks4a://...``)
jpayne@7 11 - SOCKS4 (``proxy_url='socks4://...``)
jpayne@7 12 - SOCKS5 with remote DNS (``proxy_url='socks5h://...``)
jpayne@7 13 - SOCKS5 with local DNS (``proxy_url='socks5://...``)
jpayne@7 14 - Usernames and passwords for the SOCKS proxy
jpayne@7 15
jpayne@7 16 .. note::
jpayne@7 17 It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in
jpayne@7 18 your ``proxy_url`` to ensure that DNS resolution is done from the remote
jpayne@7 19 server instead of client-side when connecting to a domain name.
jpayne@7 20
jpayne@7 21 SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5
jpayne@7 22 supports IPv4, IPv6, and domain names.
jpayne@7 23
jpayne@7 24 When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url``
jpayne@7 25 will be sent as the ``userid`` section of the SOCKS request:
jpayne@7 26
jpayne@7 27 .. code-block:: python
jpayne@7 28
jpayne@7 29 proxy_url="socks4a://<userid>@proxy-host"
jpayne@7 30
jpayne@7 31 When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion
jpayne@7 32 of the ``proxy_url`` will be sent as the username/password to authenticate
jpayne@7 33 with the proxy:
jpayne@7 34
jpayne@7 35 .. code-block:: python
jpayne@7 36
jpayne@7 37 proxy_url="socks5h://<username>:<password>@proxy-host"
jpayne@7 38
jpayne@7 39 """
jpayne@7 40
jpayne@7 41 from __future__ import annotations
jpayne@7 42
jpayne@7 43 try:
jpayne@7 44 import socks # type: ignore[import-not-found]
jpayne@7 45 except ImportError:
jpayne@7 46 import warnings
jpayne@7 47
jpayne@7 48 from ..exceptions import DependencyWarning
jpayne@7 49
jpayne@7 50 warnings.warn(
jpayne@7 51 (
jpayne@7 52 "SOCKS support in urllib3 requires the installation of optional "
jpayne@7 53 "dependencies: specifically, PySocks. For more information, see "
jpayne@7 54 "https://urllib3.readthedocs.io/en/latest/advanced-usage.html#socks-proxies"
jpayne@7 55 ),
jpayne@7 56 DependencyWarning,
jpayne@7 57 )
jpayne@7 58 raise
jpayne@7 59
jpayne@7 60 import typing
jpayne@7 61 from socket import timeout as SocketTimeout
jpayne@7 62
jpayne@7 63 from ..connection import HTTPConnection, HTTPSConnection
jpayne@7 64 from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool
jpayne@7 65 from ..exceptions import ConnectTimeoutError, NewConnectionError
jpayne@7 66 from ..poolmanager import PoolManager
jpayne@7 67 from ..util.url import parse_url
jpayne@7 68
jpayne@7 69 try:
jpayne@7 70 import ssl
jpayne@7 71 except ImportError:
jpayne@7 72 ssl = None # type: ignore[assignment]
jpayne@7 73
jpayne@7 74 from typing import TypedDict
jpayne@7 75
jpayne@7 76
jpayne@7 77 class _TYPE_SOCKS_OPTIONS(TypedDict):
jpayne@7 78 socks_version: int
jpayne@7 79 proxy_host: str | None
jpayne@7 80 proxy_port: str | None
jpayne@7 81 username: str | None
jpayne@7 82 password: str | None
jpayne@7 83 rdns: bool
jpayne@7 84
jpayne@7 85
jpayne@7 86 class SOCKSConnection(HTTPConnection):
jpayne@7 87 """
jpayne@7 88 A plain-text HTTP connection that connects via a SOCKS proxy.
jpayne@7 89 """
jpayne@7 90
jpayne@7 91 def __init__(
jpayne@7 92 self,
jpayne@7 93 _socks_options: _TYPE_SOCKS_OPTIONS,
jpayne@7 94 *args: typing.Any,
jpayne@7 95 **kwargs: typing.Any,
jpayne@7 96 ) -> None:
jpayne@7 97 self._socks_options = _socks_options
jpayne@7 98 super().__init__(*args, **kwargs)
jpayne@7 99
jpayne@7 100 def _new_conn(self) -> socks.socksocket:
jpayne@7 101 """
jpayne@7 102 Establish a new connection via the SOCKS proxy.
jpayne@7 103 """
jpayne@7 104 extra_kw: dict[str, typing.Any] = {}
jpayne@7 105 if self.source_address:
jpayne@7 106 extra_kw["source_address"] = self.source_address
jpayne@7 107
jpayne@7 108 if self.socket_options:
jpayne@7 109 extra_kw["socket_options"] = self.socket_options
jpayne@7 110
jpayne@7 111 try:
jpayne@7 112 conn = socks.create_connection(
jpayne@7 113 (self.host, self.port),
jpayne@7 114 proxy_type=self._socks_options["socks_version"],
jpayne@7 115 proxy_addr=self._socks_options["proxy_host"],
jpayne@7 116 proxy_port=self._socks_options["proxy_port"],
jpayne@7 117 proxy_username=self._socks_options["username"],
jpayne@7 118 proxy_password=self._socks_options["password"],
jpayne@7 119 proxy_rdns=self._socks_options["rdns"],
jpayne@7 120 timeout=self.timeout,
jpayne@7 121 **extra_kw,
jpayne@7 122 )
jpayne@7 123
jpayne@7 124 except SocketTimeout as e:
jpayne@7 125 raise ConnectTimeoutError(
jpayne@7 126 self,
jpayne@7 127 f"Connection to {self.host} timed out. (connect timeout={self.timeout})",
jpayne@7 128 ) from e
jpayne@7 129
jpayne@7 130 except socks.ProxyError as e:
jpayne@7 131 # This is fragile as hell, but it seems to be the only way to raise
jpayne@7 132 # useful errors here.
jpayne@7 133 if e.socket_err:
jpayne@7 134 error = e.socket_err
jpayne@7 135 if isinstance(error, SocketTimeout):
jpayne@7 136 raise ConnectTimeoutError(
jpayne@7 137 self,
jpayne@7 138 f"Connection to {self.host} timed out. (connect timeout={self.timeout})",
jpayne@7 139 ) from e
jpayne@7 140 else:
jpayne@7 141 # Adding `from e` messes with coverage somehow, so it's omitted.
jpayne@7 142 # See #2386.
jpayne@7 143 raise NewConnectionError(
jpayne@7 144 self, f"Failed to establish a new connection: {error}"
jpayne@7 145 )
jpayne@7 146 else:
jpayne@7 147 raise NewConnectionError(
jpayne@7 148 self, f"Failed to establish a new connection: {e}"
jpayne@7 149 ) from e
jpayne@7 150
jpayne@7 151 except OSError as e: # Defensive: PySocks should catch all these.
jpayne@7 152 raise NewConnectionError(
jpayne@7 153 self, f"Failed to establish a new connection: {e}"
jpayne@7 154 ) from e
jpayne@7 155
jpayne@7 156 return conn
jpayne@7 157
jpayne@7 158
jpayne@7 159 # We don't need to duplicate the Verified/Unverified distinction from
jpayne@7 160 # urllib3/connection.py here because the HTTPSConnection will already have been
jpayne@7 161 # correctly set to either the Verified or Unverified form by that module. This
jpayne@7 162 # means the SOCKSHTTPSConnection will automatically be the correct type.
jpayne@7 163 class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection):
jpayne@7 164 pass
jpayne@7 165
jpayne@7 166
jpayne@7 167 class SOCKSHTTPConnectionPool(HTTPConnectionPool):
jpayne@7 168 ConnectionCls = SOCKSConnection
jpayne@7 169
jpayne@7 170
jpayne@7 171 class SOCKSHTTPSConnectionPool(HTTPSConnectionPool):
jpayne@7 172 ConnectionCls = SOCKSHTTPSConnection
jpayne@7 173
jpayne@7 174
jpayne@7 175 class SOCKSProxyManager(PoolManager):
jpayne@7 176 """
jpayne@7 177 A version of the urllib3 ProxyManager that routes connections via the
jpayne@7 178 defined SOCKS proxy.
jpayne@7 179 """
jpayne@7 180
jpayne@7 181 pool_classes_by_scheme = {
jpayne@7 182 "http": SOCKSHTTPConnectionPool,
jpayne@7 183 "https": SOCKSHTTPSConnectionPool,
jpayne@7 184 }
jpayne@7 185
jpayne@7 186 def __init__(
jpayne@7 187 self,
jpayne@7 188 proxy_url: str,
jpayne@7 189 username: str | None = None,
jpayne@7 190 password: str | None = None,
jpayne@7 191 num_pools: int = 10,
jpayne@7 192 headers: typing.Mapping[str, str] | None = None,
jpayne@7 193 **connection_pool_kw: typing.Any,
jpayne@7 194 ):
jpayne@7 195 parsed = parse_url(proxy_url)
jpayne@7 196
jpayne@7 197 if username is None and password is None and parsed.auth is not None:
jpayne@7 198 split = parsed.auth.split(":")
jpayne@7 199 if len(split) == 2:
jpayne@7 200 username, password = split
jpayne@7 201 if parsed.scheme == "socks5":
jpayne@7 202 socks_version = socks.PROXY_TYPE_SOCKS5
jpayne@7 203 rdns = False
jpayne@7 204 elif parsed.scheme == "socks5h":
jpayne@7 205 socks_version = socks.PROXY_TYPE_SOCKS5
jpayne@7 206 rdns = True
jpayne@7 207 elif parsed.scheme == "socks4":
jpayne@7 208 socks_version = socks.PROXY_TYPE_SOCKS4
jpayne@7 209 rdns = False
jpayne@7 210 elif parsed.scheme == "socks4a":
jpayne@7 211 socks_version = socks.PROXY_TYPE_SOCKS4
jpayne@7 212 rdns = True
jpayne@7 213 else:
jpayne@7 214 raise ValueError(f"Unable to determine SOCKS version from {proxy_url}")
jpayne@7 215
jpayne@7 216 self.proxy_url = proxy_url
jpayne@7 217
jpayne@7 218 socks_options = {
jpayne@7 219 "socks_version": socks_version,
jpayne@7 220 "proxy_host": parsed.host,
jpayne@7 221 "proxy_port": parsed.port,
jpayne@7 222 "username": username,
jpayne@7 223 "password": password,
jpayne@7 224 "rdns": rdns,
jpayne@7 225 }
jpayne@7 226 connection_pool_kw["_socks_options"] = socks_options
jpayne@7 227
jpayne@7 228 super().__init__(num_pools, headers, **connection_pool_kw)
jpayne@7 229
jpayne@7 230 self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme