Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

socks.py 7.5KB

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