python-socks 2.1.1__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. {python-socks-2.1.1/python_socks.egg-info → python-socks-2.3.0}/PKG-INFO +1 -1
  2. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_version.py +1 -1
  3. python-socks-2.3.0/python_socks/async_/anyio/v2/_chain.py +26 -0
  4. python-socks-2.3.0/python_socks/async_/anyio/v2/_connect.py +16 -0
  5. python-socks-2.3.0/python_socks/async_/anyio/v2/_factory.py +11 -0
  6. python-socks-2.3.0/python_socks/async_/anyio/v2/_proxy.py +215 -0
  7. python-socks-2.3.0/python_socks/async_/anyio/v2/_stream.py +59 -0
  8. python-socks-2.3.0/python_socks/async_/asyncio/v2/_chain.py +26 -0
  9. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/asyncio/v2/_connect.py +9 -4
  10. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/asyncio/v2/_proxy.py +103 -85
  11. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/asyncio/v2/_stream.py +1 -1
  12. python-socks-2.3.0/python_socks/async_/trio/v2/_chain.py +26 -0
  13. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/trio/v2/_connect.py +4 -2
  14. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/trio/v2/_proxy.py +97 -80
  15. python-socks-2.3.0/python_socks/sync/v2/__init__.py +8 -0
  16. python-socks-2.3.0/python_socks/sync/v2/_chain.py +26 -0
  17. python-socks-2.3.0/python_socks/sync/v2/_connect.py +19 -0
  18. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/sync/v2/_proxy.py +71 -64
  19. {python-socks-2.1.1 → python-socks-2.3.0/python_socks.egg-info}/PKG-INFO +1 -1
  20. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks.egg-info/SOURCES.txt +7 -0
  21. {python-socks-2.1.1 → python-socks-2.3.0}/setup.py +1 -0
  22. python-socks-2.1.1/python_socks/async_/asyncio/v2/_chain.py +0 -31
  23. python-socks-2.1.1/python_socks/async_/trio/v2/_chain.py +0 -31
  24. python-socks-2.1.1/python_socks/sync/v2/_chain.py +0 -36
  25. {python-socks-2.1.1 → python-socks-2.3.0}/LICENSE.txt +0 -0
  26. {python-socks-2.1.1 → python-socks-2.3.0}/MANIFEST.in +0 -0
  27. {python-socks-2.1.1 → python-socks-2.3.0}/README.md +0 -0
  28. {python-socks-2.1.1 → python-socks-2.3.0}/pyproject.toml +0 -0
  29. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/__init__.py +0 -0
  30. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_abc.py +0 -0
  31. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_basic_auth.py +0 -0
  32. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_errors.py +0 -0
  33. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_helpers.py +0 -0
  34. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_proto/__init__.py +0 -0
  35. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_proto/http.py +0 -0
  36. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_proto/http_async.py +0 -0
  37. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_proto/http_sync.py +0 -0
  38. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_proto/socks4.py +0 -0
  39. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_proto/socks4_async.py +0 -0
  40. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_proto/socks4_sync.py +0 -0
  41. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_proto/socks5.py +0 -0
  42. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_proto/socks5_async.py +0 -0
  43. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_proto/socks5_sync.py +0 -0
  44. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_proxy_factory.py +0 -0
  45. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/_types.py +0 -0
  46. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/__init__.py +0 -0
  47. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/_proxy_chain.py +0 -0
  48. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/anyio/__init__.py +0 -0
  49. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/anyio/_chain.py +0 -0
  50. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/anyio/_connect.py +0 -0
  51. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/anyio/_factory.py +0 -0
  52. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/anyio/_proxy.py +0 -0
  53. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/anyio/_resolver.py +0 -0
  54. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/anyio/_stream.py +0 -0
  55. {python-socks-2.1.1/python_socks/async_/trio → python-socks-2.3.0/python_socks/async_/anyio}/v2/__init__.py +0 -0
  56. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/asyncio/__init__.py +0 -0
  57. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/asyncio/_connect.py +0 -0
  58. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/asyncio/_proxy.py +0 -0
  59. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/asyncio/_resolver.py +0 -0
  60. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/asyncio/_stream.py +0 -0
  61. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/asyncio/v2/__init__.py +0 -0
  62. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/asyncio/v2/_factory.py +0 -0
  63. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/curio/__init__.py +0 -0
  64. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/curio/_connect.py +0 -0
  65. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/curio/_proxy.py +0 -0
  66. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/curio/_resolver.py +0 -0
  67. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/curio/_stream.py +0 -0
  68. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/trio/__init__.py +0 -0
  69. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/trio/_connect.py +0 -0
  70. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/trio/_proxy.py +0 -0
  71. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/trio/_resolver.py +0 -0
  72. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/trio/_stream.py +0 -0
  73. {python-socks-2.1.1/python_socks/sync → python-socks-2.3.0/python_socks/async_/trio}/v2/__init__.py +0 -0
  74. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/trio/v2/_factory.py +0 -0
  75. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/async_/trio/v2/_stream.py +0 -0
  76. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/sync/__init__.py +0 -0
  77. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/sync/_chain.py +0 -0
  78. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/sync/_connect.py +0 -0
  79. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/sync/_proxy.py +0 -0
  80. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/sync/_resolver.py +0 -0
  81. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/sync/_stream.py +0 -0
  82. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/sync/v2/_factory.py +0 -0
  83. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/sync/v2/_ssl_transport.py +0 -0
  84. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks/sync/v2/_stream.py +0 -0
  85. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks.egg-info/dependency_links.txt +0 -0
  86. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks.egg-info/requires.txt +0 -0
  87. {python-socks-2.1.1 → python-socks-2.3.0}/python_socks.egg-info/top_level.txt +0 -0
  88. {python-socks-2.1.1 → python-socks-2.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-socks
3
- Version: 2.1.1
3
+ Version: 2.3.0
4
4
  Summary: Core proxy (SOCKS4, SOCKS5, HTTP tunneling) functionality for Python
5
5
  Home-page: https://github.com/romis2012/python-socks
6
6
  Author: Roman Snegirev
@@ -1,2 +1,2 @@
1
1
  __title__ = 'python-socks'
2
- __version__ = '2.1.1'
2
+ __version__ = '2.3.0'
@@ -0,0 +1,26 @@
1
+ from typing import Sequence
2
+ from ._proxy import AnyioProxy
3
+
4
+
5
+ class ProxyChain:
6
+ def __init__(self, proxies: Sequence[AnyioProxy]):
7
+ self._proxies = proxies
8
+
9
+ async def connect(
10
+ self,
11
+ dest_host,
12
+ dest_port,
13
+ dest_ssl=None,
14
+ timeout=None,
15
+ ):
16
+ forward = None
17
+ for proxy in self._proxies:
18
+ proxy._forward = forward
19
+ forward = proxy
20
+
21
+ return await forward.connect(
22
+ dest_host=dest_host,
23
+ dest_port=dest_port,
24
+ dest_ssl=dest_ssl,
25
+ timeout=timeout,
26
+ )
@@ -0,0 +1,16 @@
1
+ import anyio
2
+ import anyio.abc
3
+ from ._stream import AnyioSocketStream
4
+
5
+
6
+ async def connect_tcp(
7
+ host: str,
8
+ port: int,
9
+ local_host: str = None,
10
+ ) -> AnyioSocketStream:
11
+ s = await anyio.connect_tcp(
12
+ remote_host=host,
13
+ remote_port=port,
14
+ local_host=local_host,
15
+ )
16
+ return AnyioSocketStream(s)
@@ -0,0 +1,11 @@
1
+ from ...._proxy_factory import ProxyFactory
2
+ from ...._types import ProxyType
3
+ from ._proxy import Socks5Proxy, Socks4Proxy, HttpProxy, AnyioProxy
4
+
5
+
6
+ class Proxy(ProxyFactory[AnyioProxy]):
7
+ types = {
8
+ ProxyType.SOCKS4: Socks4Proxy,
9
+ ProxyType.SOCKS5: Socks5Proxy,
10
+ ProxyType.HTTP: HttpProxy,
11
+ }
@@ -0,0 +1,215 @@
1
+ import ssl
2
+
3
+ import anyio
4
+
5
+ from ._connect import connect_tcp
6
+ from ._stream import AnyioSocketStream
7
+ from .._resolver import Resolver
8
+ from ...._errors import ProxyConnectionError, ProxyTimeoutError
9
+ from ...._proto.http_async import HttpProto
10
+ from ...._proto.socks4_async import Socks4Proto
11
+ from ...._proto.socks5_async import Socks5Proto
12
+
13
+ DEFAULT_TIMEOUT = 60
14
+
15
+
16
+ class AnyioProxy:
17
+ def __init__(
18
+ self,
19
+ proxy_host: str,
20
+ proxy_port: int,
21
+ proxy_ssl: ssl.SSLContext = None,
22
+ forward: 'AnyioProxy' = None,
23
+ ):
24
+ self._proxy_host = proxy_host
25
+ self._proxy_port = proxy_port
26
+ self._proxy_ssl = proxy_ssl
27
+ self._forward = forward
28
+
29
+ self._resolver = Resolver()
30
+
31
+ async def connect(
32
+ self,
33
+ dest_host: str,
34
+ dest_port: int,
35
+ dest_ssl: ssl.SSLContext = None,
36
+ timeout: float = None,
37
+ ) -> AnyioSocketStream:
38
+ if timeout is None:
39
+ timeout = DEFAULT_TIMEOUT
40
+
41
+ try:
42
+ with anyio.fail_after(timeout):
43
+ return await self._connect(
44
+ dest_host=dest_host,
45
+ dest_port=dest_port,
46
+ dest_ssl=dest_ssl,
47
+ )
48
+ except TimeoutError as e:
49
+ raise ProxyTimeoutError('Proxy connection timed out: {}'.format(timeout)) from e
50
+
51
+ async def _connect(
52
+ self,
53
+ dest_host: str,
54
+ dest_port: int,
55
+ dest_ssl: ssl.SSLContext = None,
56
+ ) -> AnyioSocketStream:
57
+ try:
58
+ if self._forward is None:
59
+ stream = await connect_tcp(
60
+ host=self._proxy_host,
61
+ port=self._proxy_port,
62
+ )
63
+ else:
64
+ stream = await self._forward.connect(
65
+ dest_host=self._proxy_host,
66
+ dest_port=self._proxy_port,
67
+ )
68
+ except OSError as e:
69
+ raise ProxyConnectionError(
70
+ e.errno,
71
+ f"Couldn't connect to proxy {self._proxy_host}:{self._proxy_port} [{e.strerror}]",
72
+ ) from e
73
+
74
+ try:
75
+ if self._proxy_ssl is not None:
76
+ stream = await stream.start_tls(
77
+ hostname=self._proxy_host,
78
+ ssl_context=self._proxy_ssl,
79
+ )
80
+
81
+ await self._negotiate(
82
+ stream=stream,
83
+ dest_host=dest_host,
84
+ dest_port=dest_port,
85
+ )
86
+
87
+ if dest_ssl is not None:
88
+ stream = await stream.start_tls(
89
+ hostname=dest_host,
90
+ ssl_context=dest_ssl,
91
+ )
92
+ except BaseException:
93
+ with anyio.CancelScope(shield=True):
94
+ await stream.close()
95
+ raise
96
+
97
+ return stream
98
+
99
+ async def _negotiate(
100
+ self,
101
+ stream: AnyioSocketStream,
102
+ dest_host: str,
103
+ dest_port: int,
104
+ ):
105
+ raise NotImplementedError()
106
+
107
+
108
+ class Socks5Proxy(AnyioProxy):
109
+ def __init__(
110
+ self,
111
+ proxy_host,
112
+ proxy_port,
113
+ username=None,
114
+ password=None,
115
+ rdns=None,
116
+ proxy_ssl=None,
117
+ forward: 'AnyioProxy' = None,
118
+ ):
119
+ super().__init__(
120
+ proxy_host=proxy_host,
121
+ proxy_port=proxy_port,
122
+ proxy_ssl=proxy_ssl,
123
+ forward=forward,
124
+ )
125
+ self._username = username
126
+ self._password = password
127
+ self._rdns = rdns
128
+
129
+ async def _negotiate(
130
+ self,
131
+ stream: AnyioSocketStream,
132
+ dest_host: str,
133
+ dest_port: int,
134
+ ):
135
+ proto = Socks5Proto(
136
+ stream=stream,
137
+ resolver=self._resolver,
138
+ dest_host=dest_host,
139
+ dest_port=dest_port,
140
+ username=self._username,
141
+ password=self._password,
142
+ rdns=self._rdns,
143
+ )
144
+ await proto.negotiate()
145
+
146
+
147
+ class Socks4Proxy(AnyioProxy):
148
+ def __init__(
149
+ self,
150
+ proxy_host,
151
+ proxy_port,
152
+ user_id=None,
153
+ rdns=None,
154
+ proxy_ssl=None,
155
+ forward: 'AnyioProxy' = None,
156
+ ):
157
+ super().__init__(
158
+ proxy_host=proxy_host,
159
+ proxy_port=proxy_port,
160
+ proxy_ssl=proxy_ssl,
161
+ forward=forward,
162
+ )
163
+ self._user_id = user_id
164
+ self._rdns = rdns
165
+
166
+ async def _negotiate(
167
+ self,
168
+ stream: AnyioSocketStream,
169
+ dest_host: str,
170
+ dest_port: int,
171
+ ):
172
+ proto = Socks4Proto(
173
+ stream=stream,
174
+ resolver=self._resolver,
175
+ dest_host=dest_host,
176
+ dest_port=dest_port,
177
+ user_id=self._user_id,
178
+ rdns=self._rdns,
179
+ )
180
+ await proto.negotiate()
181
+
182
+
183
+ class HttpProxy(AnyioProxy):
184
+ def __init__(
185
+ self,
186
+ proxy_host,
187
+ proxy_port,
188
+ username=None,
189
+ password=None,
190
+ proxy_ssl=None,
191
+ forward: 'AnyioProxy' = None,
192
+ ):
193
+ super().__init__(
194
+ proxy_host=proxy_host,
195
+ proxy_port=proxy_port,
196
+ proxy_ssl=proxy_ssl,
197
+ forward=forward,
198
+ )
199
+ self._username = username
200
+ self._password = password
201
+
202
+ async def _negotiate(
203
+ self,
204
+ stream: AnyioSocketStream,
205
+ dest_host: str,
206
+ dest_port: int,
207
+ ):
208
+ proto = HttpProto(
209
+ stream=stream,
210
+ dest_host=dest_host,
211
+ dest_port=dest_port,
212
+ username=self._username,
213
+ password=self._password,
214
+ )
215
+ await proto.negotiate()
@@ -0,0 +1,59 @@
1
+ import ssl
2
+ from typing import Union
3
+
4
+ import anyio
5
+ import anyio.abc
6
+ from anyio.streams.tls import TLSStream
7
+
8
+ from ...._errors import ProxyError
9
+ from .... import _abc as abc
10
+
11
+ DEFAULT_RECEIVE_SIZE = 65536
12
+
13
+ AnyioStreamType = Union[anyio.abc.SocketStream, TLSStream]
14
+
15
+
16
+ class AnyioSocketStream(abc.AsyncSocketStream):
17
+ _stream: AnyioStreamType
18
+
19
+ def __init__(self, stream: AnyioStreamType) -> None:
20
+ self._stream = stream
21
+
22
+ async def write_all(self, data: bytes):
23
+ await self._stream.send(item=data)
24
+
25
+ async def read(self, max_bytes: int = DEFAULT_RECEIVE_SIZE):
26
+ try:
27
+ return await self._stream.receive(max_bytes=max_bytes)
28
+ except anyio.EndOfStream: # pragma: no cover
29
+ return b""
30
+
31
+ async def read_exact(self, n: int):
32
+ data = bytearray()
33
+ while len(data) < n:
34
+ packet = await self.read(n - len(data))
35
+ if not packet: # pragma: no cover
36
+ raise ProxyError('Connection closed unexpectedly')
37
+ data += packet
38
+ return data
39
+
40
+ async def start_tls(
41
+ self,
42
+ hostname: str,
43
+ ssl_context: ssl.SSLContext,
44
+ ) -> 'AnyioSocketStream':
45
+ ssl_stream = await TLSStream.wrap(
46
+ self._stream,
47
+ ssl_context=ssl_context,
48
+ hostname=hostname,
49
+ standard_compatible=False,
50
+ server_side=False,
51
+ )
52
+ return AnyioSocketStream(ssl_stream)
53
+
54
+ async def close(self):
55
+ await self._stream.aclose()
56
+
57
+ @property
58
+ def anyio_stream(self) -> AnyioStreamType: # pragma: no cover
59
+ return self._stream
@@ -0,0 +1,26 @@
1
+ from typing import Sequence
2
+ from ._proxy import AsyncioProxy
3
+
4
+
5
+ class ProxyChain:
6
+ def __init__(self, proxies: Sequence[AsyncioProxy]):
7
+ self._proxies = proxies
8
+
9
+ async def connect(
10
+ self,
11
+ dest_host: str,
12
+ dest_port: int,
13
+ dest_ssl=None,
14
+ timeout=None,
15
+ ):
16
+ forward = None
17
+ for proxy in self._proxies:
18
+ proxy._forward = forward
19
+ forward = proxy
20
+
21
+ return await forward.connect(
22
+ dest_host=dest_host,
23
+ dest_port=dest_port,
24
+ dest_ssl=dest_ssl,
25
+ timeout=timeout,
26
+ )
@@ -1,16 +1,17 @@
1
1
  import asyncio
2
2
  from typing import Optional, Tuple
3
+ from ._stream import AsyncioSocketStream
3
4
 
4
5
 
5
6
  async def connect_tcp(
6
7
  host: str,
7
8
  port: int,
9
+ loop: asyncio.AbstractEventLoop,
8
10
  local_addr: Optional[Tuple[str, int]] = None,
9
- ) -> Tuple[asyncio.StreamReader, asyncio.StreamWriter]:
10
-
11
+ ) -> AsyncioSocketStream:
11
12
  kwargs = {}
12
13
  if local_addr is not None:
13
- kwargs['local_addr'] = local_addr # pragma: no cover
14
+ kwargs['local_addr'] = local_addr # pragma: no cover
14
15
 
15
16
  reader, writer = await asyncio.open_connection(
16
17
  host=host,
@@ -18,4 +19,8 @@ async def connect_tcp(
18
19
  **kwargs,
19
20
  )
20
21
 
21
- return reader, writer
22
+ return AsyncioSocketStream(
23
+ loop=loop,
24
+ reader=reader,
25
+ writer=writer,
26
+ )
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import ssl
3
+ import warnings
3
4
 
4
5
  import async_timeout
5
6
 
@@ -21,8 +22,15 @@ class AsyncioProxy:
21
22
  proxy_host: str,
22
23
  proxy_port: int,
23
24
  proxy_ssl: ssl.SSLContext = None,
25
+ forward: 'AsyncioProxy' = None,
24
26
  loop: asyncio.AbstractEventLoop = None,
25
27
  ):
28
+ if loop is not None: # pragma: no cover
29
+ warnings.warn(
30
+ 'The loop argument is deprecated and scheduled for removal in the future.',
31
+ DeprecationWarning,
32
+ stacklevel=2,
33
+ )
26
34
 
27
35
  if loop is None:
28
36
  loop = asyncio.get_event_loop()
@@ -32,13 +40,8 @@ class AsyncioProxy:
32
40
  self._proxy_host = proxy_host
33
41
  self._proxy_port = proxy_port
34
42
  self._proxy_ssl = proxy_ssl
43
+ self._forward = forward
35
44
 
36
- self._dest_host = None
37
- self._dest_port = None
38
- self._dest_ssl = None
39
- self._timeout = None
40
-
41
- self._stream = None
42
45
  self._resolver = Resolver(loop=loop)
43
46
 
44
47
  async def connect(
@@ -47,83 +50,77 @@ class AsyncioProxy:
47
50
  dest_port: int,
48
51
  dest_ssl: ssl.SSLContext = None,
49
52
  timeout: float = None,
50
- _stream: AsyncioSocketStream = None,
51
53
  ) -> AsyncioSocketStream:
52
-
53
54
  if timeout is None:
54
55
  timeout = DEFAULT_TIMEOUT
55
56
 
56
- self._dest_host = dest_host
57
- self._dest_port = dest_port
58
- self._dest_ssl = dest_ssl
59
- self._timeout = timeout
60
-
61
57
  try:
62
- return await self._connect(_stream)
58
+ async with async_timeout.timeout(timeout):
59
+ return await self._connect(
60
+ dest_host=dest_host,
61
+ dest_port=dest_port,
62
+ dest_ssl=dest_ssl,
63
+ )
63
64
  except asyncio.TimeoutError as e:
64
- raise ProxyTimeoutError('Proxy connection timed out: {}'.format(self._timeout)) from e
65
-
66
- async def _connect(self, _stream: AsyncioSocketStream) -> AsyncioSocketStream:
67
- async with async_timeout.timeout(self._timeout):
68
- try:
69
- if _stream is None:
70
- reader, writer = await connect_tcp(
71
- host=self._proxy_host,
72
- port=self._proxy_port,
73
- )
74
- self._stream = AsyncioSocketStream(
75
- loop=self._loop,
76
- reader=reader,
77
- writer=writer,
78
- )
79
- else:
80
- self._stream = _stream
81
-
82
- if self._proxy_ssl is not None: # pragma: no cover
83
- self._stream = await self._stream.start_tls(
84
- hostname=self._proxy_host,
85
- ssl_context=self._proxy_ssl,
86
- )
87
- except OSError as e:
88
- await self._close()
89
- msg = 'Could not connect to proxy {}:{} [{}]'.format(
90
- self._proxy_host,
91
- self._proxy_port,
92
- e.strerror,
65
+ raise ProxyTimeoutError('Proxy connection timed out: {}'.format(timeout)) from e
66
+
67
+ async def _connect(
68
+ self,
69
+ dest_host: str,
70
+ dest_port: int,
71
+ dest_ssl: ssl.SSLContext = None,
72
+ ) -> AsyncioSocketStream:
73
+ try:
74
+ if self._forward is None:
75
+ stream = await connect_tcp(
76
+ host=self._proxy_host,
77
+ port=self._proxy_port,
78
+ loop=self._loop,
93
79
  )
94
- raise ProxyConnectionError(e.errno, msg) from e
95
- except (asyncio.CancelledError, Exception):
96
- await self._close()
97
- raise
98
-
99
- try:
100
- await self._negotiate()
101
-
102
- if self._dest_ssl is not None:
103
- self._stream = await self._stream.start_tls(
104
- hostname=self._dest_host,
105
- ssl_context=self._dest_ssl,
106
- )
107
- except (asyncio.CancelledError, Exception):
108
- await self._close()
109
- raise
110
-
111
- return self._stream
112
-
113
- async def _negotiate(self):
114
- raise NotImplementedError()
80
+ else:
81
+ stream = await self._forward.connect(
82
+ dest_host=self._proxy_host,
83
+ dest_port=self._proxy_port,
84
+ )
85
+ except OSError as e:
86
+ raise ProxyConnectionError(
87
+ e.errno,
88
+ f"Couldn't connect to proxy {self._proxy_host}:{self._proxy_port} [{e.strerror}]",
89
+ ) from e
90
+ except (asyncio.CancelledError, Exception):
91
+ raise
115
92
 
116
- async def _close(self):
117
- if self._stream is not None:
118
- await self._stream.close()
93
+ try:
94
+ if self._proxy_ssl is not None: # pragma: no cover
95
+ stream = await stream.start_tls(
96
+ hostname=self._proxy_host,
97
+ ssl_context=self._proxy_ssl,
98
+ )
119
99
 
120
- @property
121
- def proxy_host(self):
122
- return self._proxy_host
100
+ await self._negotiate(
101
+ stream=stream,
102
+ dest_host=dest_host,
103
+ dest_port=dest_port,
104
+ )
123
105
 
124
- @property
125
- def proxy_port(self):
126
- return self._proxy_port
106
+ if dest_ssl is not None:
107
+ stream = await stream.start_tls(
108
+ hostname=dest_host,
109
+ ssl_context=dest_ssl,
110
+ )
111
+ except (asyncio.CancelledError, Exception):
112
+ await stream.close()
113
+ raise
114
+
115
+ return stream
116
+
117
+ async def _negotiate(
118
+ self,
119
+ stream: AsyncioSocketStream,
120
+ dest_host: str,
121
+ dest_port: int,
122
+ ):
123
+ raise NotImplementedError()
127
124
 
128
125
 
129
126
  class Socks5Proxy(AsyncioProxy):
@@ -135,24 +132,31 @@ class Socks5Proxy(AsyncioProxy):
135
132
  password=None,
136
133
  rdns=None,
137
134
  proxy_ssl=None,
135
+ forward: 'AsyncioProxy' = None,
138
136
  loop: asyncio.AbstractEventLoop = None,
139
137
  ):
140
138
  super().__init__(
141
139
  proxy_host=proxy_host,
142
140
  proxy_port=proxy_port,
143
141
  proxy_ssl=proxy_ssl,
142
+ forward=forward,
144
143
  loop=loop,
145
144
  )
146
145
  self._username = username
147
146
  self._password = password
148
147
  self._rdns = rdns
149
148
 
150
- async def _negotiate(self):
149
+ async def _negotiate(
150
+ self,
151
+ stream: AsyncioSocketStream,
152
+ dest_host: str,
153
+ dest_port: int,
154
+ ):
151
155
  proto = Socks5Proto(
152
- stream=self._stream,
156
+ stream=stream,
153
157
  resolver=self._resolver,
154
- dest_host=self._dest_host,
155
- dest_port=self._dest_port,
158
+ dest_host=dest_host,
159
+ dest_port=dest_port,
156
160
  username=self._username,
157
161
  password=self._password,
158
162
  rdns=self._rdns,
@@ -168,23 +172,30 @@ class Socks4Proxy(AsyncioProxy):
168
172
  user_id=None,
169
173
  rdns=None,
170
174
  proxy_ssl=None,
175
+ forward: 'AsyncioProxy' = None,
171
176
  loop: asyncio.AbstractEventLoop = None,
172
177
  ):
173
178
  super().__init__(
174
179
  proxy_host=proxy_host,
175
180
  proxy_port=proxy_port,
176
181
  proxy_ssl=proxy_ssl,
182
+ forward=forward,
177
183
  loop=loop,
178
184
  )
179
185
  self._user_id = user_id
180
186
  self._rdns = rdns
181
187
 
182
- async def _negotiate(self):
188
+ async def _negotiate(
189
+ self,
190
+ stream: AsyncioSocketStream,
191
+ dest_host: str,
192
+ dest_port: int,
193
+ ):
183
194
  proto = Socks4Proto(
184
- stream=self._stream,
195
+ stream=stream,
185
196
  resolver=self._resolver,
186
- dest_host=self._dest_host,
187
- dest_port=self._dest_port,
197
+ dest_host=dest_host,
198
+ dest_port=dest_port,
188
199
  user_id=self._user_id,
189
200
  rdns=self._rdns,
190
201
  )
@@ -199,22 +210,29 @@ class HttpProxy(AsyncioProxy):
199
210
  username=None,
200
211
  password=None,
201
212
  proxy_ssl=None,
213
+ forward: 'AsyncioProxy' = None,
202
214
  loop: asyncio.AbstractEventLoop = None,
203
215
  ):
204
216
  super().__init__(
205
217
  proxy_host=proxy_host,
206
218
  proxy_port=proxy_port,
207
219
  proxy_ssl=proxy_ssl,
220
+ forward=forward,
208
221
  loop=loop,
209
222
  )
210
223
  self._username = username
211
224
  self._password = password
212
225
 
213
- async def _negotiate(self):
226
+ async def _negotiate(
227
+ self,
228
+ stream: AsyncioSocketStream,
229
+ dest_host: str,
230
+ dest_port: int,
231
+ ):
214
232
  proto = HttpProto(
215
- stream=self._stream, # noqa
216
- dest_host=self._dest_host,
217
- dest_port=self._dest_port,
233
+ stream=stream,
234
+ dest_host=dest_host,
235
+ dest_port=dest_port,
218
236
  username=self._username,
219
237
  password=self._password,
220
238
  )
@@ -17,7 +17,7 @@ async def backport_start_tls(
17
17
  ssl_handshake_timeout=None,
18
18
  ): # pragma: no cover
19
19
  """
20
- Python 3.6 asyncio doesn't have a start_tls() method on the loop
20
+ Python 3.6 asyncio doesn't have a start_tls() method on the loop,
21
21
  so we use this function in place of the loop's start_tls() method.
22
22
  Adapted from this comment:
23
23
  https://github.com/urllib3/urllib3/issues/1323#issuecomment-362494839