python-socks 2.1.1__py3-none-any.whl → 2.3.0__py3-none-any.whl

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.
python_socks/_version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  __title__ = 'python-socks'
2
- __version__ = '2.1.1'
2
+ __version__ = '2.3.0'
@@ -0,0 +1,8 @@
1
+ from ._factory import Proxy
2
+
3
+ from ._chain import ProxyChain
4
+
5
+ __all__ = (
6
+ 'Proxy',
7
+ 'ProxyChain',
8
+ )
@@ -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
@@ -1,31 +1,26 @@
1
- from typing import Iterable
1
+ from typing import Sequence
2
2
  from ._proxy import AsyncioProxy
3
3
 
4
4
 
5
5
  class ProxyChain:
6
- def __init__(self, proxies: Iterable[AsyncioProxy]):
6
+ def __init__(self, proxies: Sequence[AsyncioProxy]):
7
7
  self._proxies = proxies
8
8
 
9
- async def connect(self, dest_host, dest_port,
10
- dest_ssl=None, timeout=None,):
11
- _stream = None
12
- proxies = list(self._proxies)
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
13
20
 
14
- length = len(proxies) - 1
15
- for i in range(length):
16
- _stream = await proxies[i].connect(
17
- dest_host=proxies[i + 1].proxy_host,
18
- dest_port=proxies[i + 1].proxy_port,
19
- timeout=timeout,
20
- _stream=_stream
21
- )
22
-
23
- _stream = await proxies[length].connect(
21
+ return await forward.connect(
24
22
  dest_host=dest_host,
25
23
  dest_port=dest_port,
26
24
  dest_ssl=dest_ssl,
27
25
  timeout=timeout,
28
- _stream=_stream
29
26
  )
30
-
31
- return _stream
@@ -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
+ )