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