11try :
22 import aiohttp
33 from aiohttp .connector import sentinel
4+ from aiohttp .client_exceptions import certificate_errors , ssl_errors
45except ImportError :
56 raise ImportError ('aiosocks.SocksConnector require aiohttp library' )
6-
7- from yarl import URL
8- from urllib .request import getproxies
9-
10- from .errors import SocksError , SocksConnectionError
7+ from .errors import SocksConnectionError
118from .helpers import Socks4Auth , Socks5Auth , Socks4Addr , Socks5Addr
129from . import create_connection
1310
1411__all__ = ('ProxyConnector' , 'ProxyClientRequest' )
1512
1613
17- class ProxyClientRequest (aiohttp .ClientRequest ):
18- def update_proxy (self , proxy , proxy_auth , proxy_from_env ):
19- if proxy_from_env and not proxy :
20- proxies = getproxies ()
14+ from distutils .version import StrictVersion
2115
22- proxy_url = proxies .get (self .original_url .scheme )
23- if not proxy_url :
24- proxy_url = proxies .get ('socks4' ) or proxies .get ('socks5' )
16+ if StrictVersion (aiohttp .__version__ ) < StrictVersion ('2.3.2' ):
17+ raise RuntimeError ('aiosocks.connector depends on aiohttp 2.3.2+' )
2518
26- proxy = URL (proxy_url ) if proxy_url else None
2719
20+ class ProxyClientRequest (aiohttp .ClientRequest ):
21+ def update_proxy (self , proxy , proxy_auth , proxy_headers ):
2822 if proxy and proxy .scheme not in ['http' , 'socks4' , 'socks5' ]:
2923 raise ValueError (
3024 "Only http, socks4 and socks5 proxies are supported" )
@@ -41,9 +35,9 @@ def update_proxy(self, proxy, proxy_auth, proxy_from_env):
4135 not isinstance (proxy_auth , Socks5Auth ):
4236 raise ValueError ("proxy_auth must be None or Socks5Auth() "
4337 "tuple for socks5 proxy" )
44-
4538 self .proxy = proxy
4639 self .proxy_auth = proxy_auth
40+ self .proxy_headers = proxy_headers
4741
4842
4943class ProxyConnector (aiohttp .TCPConnector ):
@@ -69,20 +63,41 @@ async def _create_proxy_connection(self, req):
6963 else :
7064 return await self ._create_socks_connection (req )
7165
66+ async def _wrap_create_socks_connection (self , * args , req , ** kwargs ):
67+ try :
68+ return await create_connection (* args , ** kwargs )
69+ except certificate_errors as exc :
70+ raise aiohttp .ClientConnectorCertificateError (
71+ req .connection_key , exc ) from exc
72+ except ssl_errors as exc :
73+ raise aiohttp .ClientConnectorSSLError (
74+ req .connection_key , exc ) from exc
75+ except (OSError , SocksConnectionError ) as exc :
76+ raise aiohttp .ClientProxyConnectionError (
77+ req .connection_key , exc ) from exc
78+
7279 async def _create_socks_connection (self , req ):
73- if req .ssl :
74- sslcontext = self .ssl_context
75- else :
76- sslcontext = None
80+ sslcontext = self ._get_ssl_context (req )
81+ fingerprint , hashfunc = self ._get_fingerprint_and_hashfunc (req )
7782
7883 if not self ._remote_resolve :
79- dst_hosts = list (await self ._resolve_host (req .host , req .port ))
80- dst = dst_hosts [0 ]['host' ], dst_hosts [0 ]['port' ]
84+ try :
85+ dst_hosts = list (await self ._resolve_host (req .host , req .port ))
86+ dst = dst_hosts [0 ]['host' ], dst_hosts [0 ]['port' ]
87+ except OSError as exc :
88+ raise aiohttp .ClientConnectorError (
89+ req .connection_key , exc ) from exc
8190 else :
8291 dst = req .host , req .port
8392
84- proxy_hosts = await self ._resolve_host (req .proxy .host , req .proxy .port )
85- exc = None
93+ try :
94+ proxy_hosts = await self ._resolve_host (
95+ req .proxy .host , req .proxy .port )
96+ except OSError as exc :
97+ raise aiohttp .ClientConnectorError (
98+ req .connection_key , exc ) from exc
99+
100+ last_exc = None
86101
87102 for hinfo in proxy_hosts :
88103 if req .proxy .scheme == 'socks4' :
@@ -91,45 +106,37 @@ async def _create_socks_connection(self, req):
91106 proxy = Socks5Addr (hinfo ['host' ], hinfo ['port' ])
92107
93108 try :
94- transp , proto = await create_connection (
109+ transp , proto = await self . _wrap_create_socks_connection (
95110 self ._factory , proxy , req .proxy_auth , dst ,
96111 loop = self ._loop , remote_resolve = self ._remote_resolve ,
97112 ssl = sslcontext , family = hinfo ['family' ],
98113 proto = hinfo ['proto' ], flags = hinfo ['flags' ],
99- local_addr = self ._local_addr ,
114+ local_addr = self ._local_addr , req = req ,
100115 server_hostname = req .host if sslcontext else None )
101-
102- self ._validate_ssl_fingerprint (transp , req .host , req .port )
103- return transp , proto
104- except (OSError , SocksError , SocksConnectionError ) as e :
105- exc = e
116+ except aiohttp .ClientConnectorError as exc :
117+ last_exc = exc
118+ continue
119+
120+ has_cert = transp .get_extra_info ('sslcontext' )
121+ if has_cert and fingerprint :
122+ sock = transp .get_extra_info ('socket' )
123+ if not hasattr (sock , 'getpeercert' ):
124+ # Workaround for asyncio 3.5.0
125+ # Starting from 3.5.1 version
126+ # there is 'ssl_object' extra info in transport
127+ sock = transp ._ssl_protocol ._sslpipe .ssl_object
128+ # gives DER-encoded cert as a sequence of bytes (or None)
129+ cert = sock .getpeercert (binary_form = True )
130+ assert cert
131+ got = hashfunc (cert ).digest ()
132+ expected = fingerprint
133+ if got != expected :
134+ transp .close ()
135+ if not self ._cleanup_closed_disabled :
136+ self ._cleanup_closed_transports .append (transp )
137+ last_exc = aiohttp .ServerFingerprintMismatch (
138+ expected , got , req .host , req .port )
139+ continue
140+ return transp , proto
106141 else :
107- if isinstance (exc , SocksConnectionError ):
108- raise aiohttp .ClientProxyConnectionError (* exc .args )
109- if isinstance (exc , SocksError ):
110- raise exc
111- else :
112- raise aiohttp .ClientOSError (
113- exc .errno , 'Can not connect to %s:%s [%s]' %
114- (req .host , req .port , exc .strerror )) from exc
115-
116- def _validate_ssl_fingerprint (self , transp , host , port ):
117- has_cert = transp .get_extra_info ('sslcontext' )
118- if has_cert and self ._fingerprint :
119- sock = transp .get_extra_info ('socket' )
120- if not hasattr (sock , 'getpeercert' ):
121- # Workaround for asyncio 3.5.0
122- # Starting from 3.5.1 version
123- # there is 'ssl_object' extra info in transport
124- sock = transp ._ssl_protocol ._sslpipe .ssl_object
125- # gives DER-encoded cert as a sequence of bytes (or None)
126- cert = sock .getpeercert (binary_form = True )
127- assert cert
128- got = self ._hashfunc (cert ).digest ()
129- expected = self ._fingerprint
130- if got != expected :
131- transp .close ()
132- if not self ._cleanup_closed_disabled :
133- self ._cleanup_closed_transports .append (transp )
134- raise aiohttp .ServerFingerprintMismatch (
135- expected , got , host , port )
142+ raise last_exc
0 commit comments