pax_global_header00006660000000000000000000000064147474620510014524gustar00rootroot0000000000000052 comment=8eb860cbb89c15d16cf94b99c842752ee90f3e0f python-socks-2.7.1/000077500000000000000000000000001474746205100141745ustar00rootroot00000000000000python-socks-2.7.1/.coveragerc000066400000000000000000000005431474746205100163170ustar00rootroot00000000000000[run] omit = # */_proxy_chain_*.py # python_socks/async_/asyncio/ext/* python_socks/_basic_auth.py python_socks/sync/v2/_ssl_transport.py [report] # Regexes for lines to exclude from consideration exclude_lines = pragma: no cover pragma: nocover def __repr__ if self.debug: raise NotImplementedError raise ValueError python-socks-2.7.1/.flake8000066400000000000000000000000611474746205100153440ustar00rootroot00000000000000[flake8] ignore = N805,W503 max-line-length = 99 python-socks-2.7.1/.github/000077500000000000000000000000001474746205100155345ustar00rootroot00000000000000python-socks-2.7.1/.github/workflows/000077500000000000000000000000001474746205100175715ustar00rootroot00000000000000python-socks-2.7.1/.github/workflows/ci.yml000066400000000000000000000023751474746205100207160ustar00rootroot00000000000000name: CI on: push: branches: ["master"] pull_request: branches: ["master"] jobs: build: name: "Python ${{ matrix.python-version }} ${{ matrix.os }}" runs-on: "${{ matrix.os }}" strategy: matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] os: [ubuntu-latest] steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip setuptools pip install -r requirements-dev.txt - name: Lint with flake8 run: | python -m flake8 python_socks tests continue-on-error: true - name: Run tests # run: python -m pytest tests --cov=./python_socks --cov-report term-missing -s run: python -m pytest tests --cov=./python_socks --cov-report xml - name: Upload coverage uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} slug: romis2012/python-socks file: ./coverage.xml flags: unit fail_ci_if_error: falsepython-socks-2.7.1/.gitignore000066400000000000000000000005301474746205100161620ustar00rootroot00000000000000*.bak *.egg *.egg-info *.eggs *.pyc *.pyd *.pyo *.so *.tar.gz *~ .DS_Store .Python .cache .coverage .coverage.* .idea .installed.cfg .noseids .tox .vimrc # bin build cover coverage develop-eggs dist docs/_build/ eggs include/ lib/ man/ nosetests.xml parts pyvenv sources var/* venv virtualenv.py .install-deps .develop .idea/ .vscode/ usage*.pypython-socks-2.7.1/LICENSE.txt000066400000000000000000000261351474746205100160260ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. python-socks-2.7.1/MANIFEST.in000066400000000000000000000001111474746205100157230ustar00rootroot00000000000000# Include the license file include LICENSE.txt recursive-include tests * python-socks-2.7.1/README.md000066400000000000000000000142661474746205100154640ustar00rootroot00000000000000## python-socks [![CI](https://github.com/romis2012/python-socks/actions/workflows/ci.yml/badge.svg)](https://github.com/romis2012/python-socks/actions/workflows/ci.yml) [![Coverage Status](https://codecov.io/gh/romis2012/python-socks/branch/master/graph/badge.svg)](https://codecov.io/gh/romis2012/python-socks) [![PyPI version](https://badge.fury.io/py/python-socks.svg)](https://pypi.python.org/pypi/python-socks) The `python-socks` package provides a core proxy client functionality for Python. Supports `SOCKS4(a)`, `SOCKS5(h)`, `HTTP CONNECT` proxy and provides sync and async (asyncio, trio, curio, anyio) APIs. You probably don't need to use `python-socks` directly. It is used internally by [aiohttp-socks](https://github.com/romis2012/aiohttp-socks) and [httpx-socks](https://github.com/romis2012/httpx-socks) packages. ## Requirements - Python >= 3.8 - async-timeout >= 4.0 (optional) - trio >= 0.24 (optional) - curio >= 1.4 (optional) - anyio >= 3.3.4 (optional) ## Installation only sync proxy support: ``` pip install python-socks ``` to include optional asyncio support: ``` pip install python-socks[asyncio] ``` to include optional trio support: ``` pip install python-socks[trio] ``` to include optional curio support: ``` pip install python-socks[curio] ``` to include optional anyio support: ``` pip install python-socks[anyio] ``` ## Simple usage We are making secure HTTP GET request via SOCKS5 proxy #### Sync ```python import ssl from python_socks.sync import Proxy proxy = Proxy.from_url('socks5://user:password@127.0.0.1:1080') # `connect` returns standard Python socket in blocking mode sock = proxy.connect(dest_host='check-host.net', dest_port=443) sock = ssl.create_default_context().wrap_socket( sock=sock, server_hostname='check-host.net' ) request = ( b'GET /ip HTTP/1.1\r\n' b'Host: check-host.net\r\n' b'Connection: close\r\n\r\n' ) sock.sendall(request) response = sock.recv(4096) print(response) ``` #### Async (asyncio) ```python import ssl import asyncio from python_socks.async_.asyncio import Proxy proxy = Proxy.from_url('socks5://user:password@127.0.0.1:1080') # `connect` returns standard Python socket in non-blocking mode # so we can pass it to asyncio.open_connection(...) sock = await proxy.connect(dest_host='check-host.net', dest_port=443) reader, writer = await asyncio.open_connection( host=None, port=None, sock=sock, ssl=ssl.create_default_context(), server_hostname='check-host.net', ) request = ( b'GET /ip HTTP/1.1\r\n' b'Host: check-host.net\r\n' b'Connection: close\r\n\r\n' ) writer.write(request) response = await reader.read(-1) print(response) ``` #### Async (trio) ```python import ssl import trio from python_socks.async_.trio import Proxy proxy = Proxy.from_url('socks5://user:password@127.0.0.1:1080') # `connect` returns trio socket # so we can pass it to trio.SocketStream sock = await proxy.connect(dest_host='check-host.net', dest_port=443) stream = trio.SocketStream(sock) stream = trio.SSLStream( stream, ssl.create_default_context(), server_hostname='check-host.net' ) await stream.do_handshake() request = ( b'GET /ip HTTP/1.1\r\n' b'Host: check-host.net\r\n' b'Connection: close\r\n\r\n' ) await stream.send_all(request) response = await stream.receive_some(4096) print(response) ``` #### Async (curio) ```python import curio.ssl as curiossl from python_socks.async_.curio import Proxy proxy = Proxy.from_url('socks5://user:password@127.0.0.1:1080') # `connect` returns curio.io.Socket sock = await proxy.connect( dest_host='check-host.net', dest_port=443 ) request = ( b'GET /ip HTTP/1.1\r\n' b'Host: check-host.net\r\n' b'Connection: close\r\n\r\n' ) ssl_context = curiossl.create_default_context() sock = await ssl_context.wrap_socket( sock, do_handshake_on_connect=False, server_hostname='check-host.net' ) await sock.do_handshake() stream = sock.as_stream() await stream.write(request) response = await stream.read(1024) print(response) ``` #### Async (anyio) ```python import ssl from python_socks.async_.anyio import Proxy proxy = Proxy.from_url('socks5://user:password@127.0.0.1:1080') # `connect` returns AnyioSocketStream stream = await proxy.connect( dest_host='check-host.net', dest_port=443, dest_ssl=ssl.create_default_context(), ) request = ( b'GET /ip HTTP/1.1\r\n' b'Host: check-host.net\r\n' b'Connection: close\r\n\r\n' ) await stream.write_all(request) response = await stream.read() print(response) ``` ## More complex example #### A urllib3 PoolManager that routes connections via the proxy ```python from urllib3 import PoolManager, HTTPConnectionPool, HTTPSConnectionPool from urllib3.connection import HTTPConnection, HTTPSConnection from python_socks.sync import Proxy class ProxyHTTPConnection(HTTPConnection): def __init__(self, *args, **kwargs): socks_options = kwargs.pop('_socks_options') self._proxy_url = socks_options['proxy_url'] super().__init__(*args, **kwargs) def _new_conn(self): proxy = Proxy.from_url(self._proxy_url) return proxy.connect( dest_host=self.host, dest_port=self.port, timeout=self.timeout ) class ProxyHTTPSConnection(ProxyHTTPConnection, HTTPSConnection): pass class ProxyHTTPConnectionPool(HTTPConnectionPool): ConnectionCls = ProxyHTTPConnection class ProxyHTTPSConnectionPool(HTTPSConnectionPool): ConnectionCls = ProxyHTTPSConnection class ProxyPoolManager(PoolManager): def __init__(self, proxy_url, timeout=5, num_pools=10, headers=None, **connection_pool_kw): connection_pool_kw['_socks_options'] = {'proxy_url': proxy_url} connection_pool_kw['timeout'] = timeout super().__init__(num_pools, headers, **connection_pool_kw) self.pool_classes_by_scheme = { 'http': ProxyHTTPConnectionPool, 'https': ProxyHTTPSConnectionPool, } ### and how to use it manager = ProxyPoolManager('socks5://user:password@127.0.0.1:1080') response = manager.request('GET', 'https://check-host.net/ip') print(response.data) ``` python-socks-2.7.1/pyproject.toml000066400000000000000000000035631474746205100171170ustar00rootroot00000000000000[build-system] requires = ['setuptools'] build-backend = 'setuptools.build_meta' [project] name = 'python-socks' license = { text = 'Apache-2.0' } description = 'Proxy (SOCKS4, SOCKS5, HTTP CONNECT) client for Python' readme = 'README.md' authors = [{ name = 'Roman Snegirev', email = 'snegiryev@gmail.com' }] keywords = [ 'socks', 'socks5', 'socks4', 'http', 'proxy', 'asyncio', 'trio', 'curio', 'anyio', ] requires-python = ">=3.8.0" dynamic = ['version'] classifiers = [ "Development Status :: 5 - Production/Stable", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Operating System :: MacOS", "Operating System :: Microsoft", "Operating System :: POSIX :: Linux", "Topic :: Internet :: WWW/HTTP", "Intended Audience :: Developers", "Framework :: AsyncIO", "Framework :: Trio", "License :: OSI Approved :: Apache Software License", ] [project.optional-dependencies] asyncio = ['async-timeout>=4.0; python_version < "3.11"'] trio = ['trio>=0.24'] curio = ['curio>=1.4'] anyio = ['anyio>=3.3.4,<5.0.0'] [project.urls] homepage = 'https://github.com/romis2012/python-socks' repository = 'https://github.com/romis2012/python-socks' [tool.setuptools.dynamic] version = { attr = 'python_socks.__version__' } [tool.setuptools.packages.find] include = ['python_socks*'] [tool.black] line-length = 99 target-version = ['py38', 'py39', 'py310', 'py311', 'py312'] skip-string-normalization = true preview = true verbose = true [tool.pytest.ini_options] asyncio_mode = 'strict' python-socks-2.7.1/python_socks/000077500000000000000000000000001474746205100167175ustar00rootroot00000000000000python-socks-2.7.1/python_socks/__init__.py000066400000000000000000000005571474746205100210370ustar00rootroot00000000000000from ._version import __version__, __title__ from ._types import ProxyType from ._helpers import parse_proxy_url from ._errors import ( ProxyError, ProxyTimeoutError, ProxyConnectionError, ) __all__ = ( '__title__', '__version__', 'ProxyError', 'ProxyTimeoutError', 'ProxyConnectionError', 'ProxyType', 'parse_proxy_url', ) python-socks-2.7.1/python_socks/_abc.py000066400000000000000000000016151474746205100201600ustar00rootroot00000000000000from typing import Optional class SyncResolver: def resolve(self, host, port=0, family=0): raise NotImplementedError() class AsyncResolver: async def resolve(self, host, port=0, family=0): raise NotImplementedError() class SyncSocketStream: def write_all(self, data: bytes): raise NotImplementedError() def read(self, max_bytes: Optional[int] = None): raise NotImplementedError() def read_exact(self, n: int): raise NotImplementedError() def close(self): raise NotImplementedError() class AsyncSocketStream: async def write_all(self, data: bytes): raise NotImplementedError() async def read(self, max_bytes: Optional[int] = None): raise NotImplementedError() async def read_exact(self, n: int): raise NotImplementedError() async def close(self): raise NotImplementedError() python-socks-2.7.1/python_socks/_connectors/000077500000000000000000000000001474746205100212335ustar00rootroot00000000000000python-socks-2.7.1/python_socks/_connectors/__init__.py000066400000000000000000000000001474746205100233320ustar00rootroot00000000000000python-socks-2.7.1/python_socks/_connectors/abc.py000066400000000000000000000006151474746205100223340ustar00rootroot00000000000000from .._abc import SyncSocketStream, AsyncSocketStream class SyncConnector: def connect( self, stream: SyncSocketStream, host: str, port: int, ): raise NotImplementedError class AsyncConnector: async def connect( self, stream: AsyncSocketStream, host: str, port: int, ): raise NotImplementedError python-socks-2.7.1/python_socks/_connectors/factory_async.py000066400000000000000000000020371474746205100244530ustar00rootroot00000000000000from typing import Optional from .._abc import AsyncResolver from .._types import ProxyType from .abc import AsyncConnector from .socks5_async import Socks5AsyncConnector from .socks4_async import Socks4AsyncConnector from .http_async import HttpAsyncConnector def create_connector( proxy_type: ProxyType, username: Optional[str], password: Optional[str], rdns: Optional[bool], resolver: AsyncResolver, ) -> AsyncConnector: if proxy_type == ProxyType.SOCKS4: return Socks4AsyncConnector( user_id=username, rdns=rdns, resolver=resolver, ) if proxy_type == ProxyType.SOCKS5: return Socks5AsyncConnector( username=username, password=password, rdns=rdns, resolver=resolver, ) if proxy_type == ProxyType.HTTP: return HttpAsyncConnector( username=username, password=password, resolver=resolver, ) raise ValueError(f'Invalid proxy type: {proxy_type}') python-socks-2.7.1/python_socks/_connectors/factory_sync.py000066400000000000000000000020221474746205100243040ustar00rootroot00000000000000from typing import Optional from .._abc import SyncResolver from .._types import ProxyType from .abc import SyncConnector from .socks5_sync import Socks5SyncConnector from .socks4_sync import Socks4SyncConnector from .http_sync import HttpSyncConnector def create_connector( proxy_type: ProxyType, username: Optional[str], password: Optional[str], rdns: Optional[bool], resolver: SyncResolver, ) -> SyncConnector: if proxy_type == ProxyType.SOCKS4: return Socks4SyncConnector( user_id=username, rdns=rdns, resolver=resolver, ) if proxy_type == ProxyType.SOCKS5: return Socks5SyncConnector( username=username, password=password, rdns=rdns, resolver=resolver, ) if proxy_type == ProxyType.HTTP: return HttpSyncConnector( username=username, password=password, resolver=resolver, ) raise ValueError(f'Invalid proxy type: {proxy_type}') python-socks-2.7.1/python_socks/_connectors/http_async.py000066400000000000000000000016671474746205100237730ustar00rootroot00000000000000from typing import Optional from .._abc import AsyncSocketStream, AsyncResolver from .abc import AsyncConnector from .._protocols import http class HttpAsyncConnector(AsyncConnector): def __init__( self, username: Optional[str], password: Optional[str], resolver: AsyncResolver, ): self._username = username self._password = password self._resolver = resolver async def connect( self, stream: AsyncSocketStream, host: str, port: int, ) -> http.ConnectReply: conn = http.Connection() request = http.ConnectRequest( host=host, port=port, username=self._username, password=self._password, ) data = conn.send(request) await stream.write_all(data) data = await stream.read() reply: http.ConnectReply = conn.receive(data) return reply python-socks-2.7.1/python_socks/_connectors/http_sync.py000066400000000000000000000016361474746205100236260ustar00rootroot00000000000000from typing import Optional from .._abc import SyncSocketStream, SyncResolver from .abc import SyncConnector from .._protocols import http class HttpSyncConnector(SyncConnector): def __init__( self, username: Optional[str], password: Optional[str], resolver: SyncResolver, ): self._username = username self._password = password self._resolver = resolver def connect( self, stream: SyncSocketStream, host: str, port: int, ) -> http.ConnectReply: conn = http.Connection() request = http.ConnectRequest( host=host, port=port, username=self._username, password=self._password, ) data = conn.send(request) stream.write_all(data) data = stream.read() reply: http.ConnectReply = conn.receive(data) return reply python-socks-2.7.1/python_socks/_connectors/socks4_async.py000066400000000000000000000022331474746205100242100ustar00rootroot00000000000000import socket from typing import Optional from .._abc import AsyncSocketStream, AsyncResolver from .abc import AsyncConnector from .._protocols import socks4 from .._helpers import is_ip_address class Socks4AsyncConnector(AsyncConnector): def __init__( self, user_id: Optional[str], rdns: Optional[bool], resolver: AsyncResolver, ): if rdns is None: rdns = False self._user_id = user_id self._rdns = rdns self._resolver = resolver async def connect( self, stream: AsyncSocketStream, host: str, port: int, ) -> socks4.ConnectReply: conn = socks4.Connection() if not is_ip_address(host) and not self._rdns: _, host = await self._resolver.resolve( host, family=socket.AF_INET, ) request = socks4.ConnectRequest(host=host, port=port, user_id=self._user_id) data = conn.send(request) await stream.write_all(data) data = await stream.read_exact(socks4.ConnectReply.SIZE) reply: socks4.ConnectReply = conn.receive(data) return reply python-socks-2.7.1/python_socks/_connectors/socks4_sync.py000066400000000000000000000021741474746205100240530ustar00rootroot00000000000000import socket from typing import Optional from .._abc import SyncSocketStream, SyncResolver from .abc import SyncConnector from .._protocols import socks4 from .._helpers import is_ip_address class Socks4SyncConnector(SyncConnector): def __init__( self, user_id: Optional[str], rdns: Optional[bool], resolver: SyncResolver, ): if rdns is None: rdns = False self._user_id = user_id self._rdns = rdns self._resolver = resolver def connect( self, stream: SyncSocketStream, host: str, port: int, ) -> socks4.ConnectReply: conn = socks4.Connection() if not is_ip_address(host) and not self._rdns: _, host = self._resolver.resolve( host, family=socket.AF_INET, ) request = socks4.ConnectRequest(host=host, port=port, user_id=self._user_id) data = conn.send(request) stream.write_all(data) data = stream.read_exact(socks4.ConnectReply.SIZE) reply: socks4.ConnectReply = conn.receive(data) return reply python-socks-2.7.1/python_socks/_connectors/socks5_async.py000066400000000000000000000053421474746205100242150ustar00rootroot00000000000000import socket from typing import Optional from .._abc import AsyncSocketStream, AsyncResolver from .abc import AsyncConnector from .._protocols import socks5 from .._helpers import is_ip_address class Socks5AsyncConnector(AsyncConnector): def __init__( self, username: Optional[str], password: Optional[str], rdns: Optional[bool], resolver: AsyncResolver, ): if rdns is None: rdns = True self._username = username self._password = password self._rdns = rdns self._resolver = resolver async def connect( self, stream: AsyncSocketStream, host: str, port: int, ) -> socks5.ConnectReply: conn = socks5.Connection() # Auth methods request = socks5.AuthMethodsRequest( username=self._username, password=self._password, ) data = conn.send(request) await stream.write_all(data) data = await stream.read_exact(socks5.AuthMethodReply.SIZE) reply: socks5.AuthMethodReply = conn.receive(data) # Authenticate if reply.method == socks5.AuthMethod.USERNAME_PASSWORD: request = socks5.AuthRequest( username=self._username, password=self._password, ) data = conn.send(request) await stream.write_all(data) data = await stream.read_exact(socks5.AuthReply.SIZE) _: socks5.AuthReply = conn.receive(data) # Connect if not is_ip_address(host) and not self._rdns: _, host = await self._resolver.resolve( host, family=socket.AF_UNSPEC, ) request = socks5.ConnectRequest(host=host, port=port) data = conn.send(request) await stream.write_all(data) data = await self._read_reply(stream) reply: socks5.ConnectReply = conn.receive(data) return reply # noinspection PyMethodMayBeStatic async def _read_reply(self, stream: AsyncSocketStream) -> bytes: data = await stream.read_exact(4) if data[0] != socks5.SOCKS_VER: return data if data[1] != socks5.ReplyCode.SUCCEEDED: return data if data[2] != socks5.RSV: return data addr_type = data[3] if addr_type == socks5.AddressType.IPV4: data += await stream.read_exact(6) elif addr_type == socks5.AddressType.IPV6: data += await stream.read_exact(18) elif addr_type == socks5.AddressType.DOMAIN: data += await stream.read_exact(1) host_len = data[-1] data += await stream.read_exact(host_len + 2) return data python-socks-2.7.1/python_socks/_connectors/socks5_sync.py000066400000000000000000000050051474746205100240500ustar00rootroot00000000000000import socket from typing import Optional from .._abc import SyncSocketStream, SyncResolver from .abc import SyncConnector from .._protocols import socks5 from .._helpers import is_ip_address class Socks5SyncConnector(SyncConnector): def __init__( self, username: Optional[str], password: Optional[str], rdns: Optional[bool], resolver: SyncResolver, ): if rdns is None: rdns = True self._username = username self._password = password self._rdns = rdns self._resolver = resolver def connect( self, stream: SyncSocketStream, host: str, port: int, ) -> socks5.ConnectReply: conn = socks5.Connection() # Auth methods request = socks5.AuthMethodsRequest(username=self._username, password=self._password) data = conn.send(request) stream.write_all(data) data = stream.read_exact(socks5.AuthMethodReply.SIZE) reply: socks5.AuthMethodReply = conn.receive(data) # Authenticate if reply.method == socks5.AuthMethod.USERNAME_PASSWORD: request = socks5.AuthRequest(username=self._username, password=self._password) data = conn.send(request) stream.write_all(data) data = stream.read_exact(socks5.AuthReply.SIZE) _: socks5.AuthReply = conn.receive(data) # Connect if not is_ip_address(host) and not self._rdns: _, host = self._resolver.resolve(host, family=socket.AF_UNSPEC) request = socks5.ConnectRequest(host=host, port=port) data = conn.send(request) stream.write_all(data) data = self._read_reply(stream) reply: socks5.ConnectReply = conn.receive(data) return reply # noinspection PyMethodMayBeStatic def _read_reply(self, stream: SyncSocketStream) -> bytes: data = stream.read_exact(4) if data[0] != socks5.SOCKS_VER: return data if data[1] != socks5.ReplyCode.SUCCEEDED: return data if data[2] != socks5.RSV: return data addr_type = data[3] if addr_type == socks5.AddressType.IPV4: data += stream.read_exact(6) elif addr_type == socks5.AddressType.IPV6: data += stream.read_exact(18) elif addr_type == socks5.AddressType.DOMAIN: data += stream.read_exact(1) host_len = data[-1] data += stream.read_exact(host_len + 2) return data python-socks-2.7.1/python_socks/_errors.py000066400000000000000000000003701474746205100207440ustar00rootroot00000000000000class ProxyError(Exception): def __init__(self, message, error_code=None): super().__init__(message) self.error_code = error_code class ProxyTimeoutError(TimeoutError): pass class ProxyConnectionError(OSError): pass python-socks-2.7.1/python_socks/_helpers.py000066400000000000000000000052241474746205100210750ustar00rootroot00000000000000import functools import re from typing import Optional, Tuple from urllib.parse import urlparse, unquote from ._types import ProxyType # pylint:disable-next=invalid-name _ipv4_pattern = ( r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}' r'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$' ) # pylint:disable-next=invalid-name _ipv6_pattern = ( r'^(?:(?:(?:[A-F0-9]{1,4}:){6}|(?=(?:[A-F0-9]{0,4}:){0,6}' r'(?:[0-9]{1,3}\.){3}[0-9]{1,3}$)(([0-9A-F]{1,4}:){0,5}|:)' r'((:[0-9A-F]{1,4}){1,5}:|:)|::(?:[A-F0-9]{1,4}:){5})' r'(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}' r'(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])|(?:[A-F0-9]{1,4}:){7}' r'[A-F0-9]{1,4}|(?=(?:[A-F0-9]{0,4}:){0,7}[A-F0-9]{0,4}$)' r'(([0-9A-F]{1,4}:){1,7}|:)((:[0-9A-F]{1,4}){1,7}|:)|(?:[A-F0-9]{1,4}:){7}' r':|:(:[A-F0-9]{1,4}){7})$' ) _ipv4_regex = re.compile(_ipv4_pattern) _ipv6_regex = re.compile(_ipv6_pattern, flags=re.IGNORECASE) _ipv4_regexb = re.compile(_ipv4_pattern.encode('ascii')) _ipv6_regexb = re.compile(_ipv6_pattern.encode('ascii'), flags=re.IGNORECASE) def _is_ip_address(regex, regexb, host): # if host is None: # return False if isinstance(host, str): return bool(regex.match(host)) elif isinstance(host, (bytes, bytearray, memoryview)): return bool(regexb.match(host)) else: raise TypeError( '{} [{}] is not a str or bytes'.format(host, type(host)) # pragma: no cover ) is_ipv4_address = functools.partial(_is_ip_address, _ipv4_regex, _ipv4_regexb) is_ipv6_address = functools.partial(_is_ip_address, _ipv6_regex, _ipv6_regexb) def is_ip_address(host): return is_ipv4_address(host) or is_ipv6_address(host) def parse_proxy_url(url: str) -> Tuple[ProxyType, str, int, Optional[str], Optional[str]]: parsed = urlparse(url) scheme = parsed.scheme if scheme == 'socks5': proxy_type = ProxyType.SOCKS5 elif scheme == 'socks4': proxy_type = ProxyType.SOCKS4 elif scheme == 'http': proxy_type = ProxyType.HTTP else: raise ValueError(f'Invalid scheme component: {scheme}') # pragma: no cover host = parsed.hostname if not host: raise ValueError('Empty host component') # pragma: no cover try: port = parsed.port assert port is not None except (ValueError, TypeError, AssertionError) as e: # pragma: no cover raise ValueError('Invalid port component') from e try: username, password = (unquote(parsed.username), unquote(parsed.password)) except (AttributeError, TypeError): username, password = '', '' return proxy_type, host, port, username, password python-socks-2.7.1/python_socks/_protocols/000077500000000000000000000000001474746205100211025ustar00rootroot00000000000000python-socks-2.7.1/python_socks/_protocols/__init__.py000066400000000000000000000000001474746205100232010ustar00rootroot00000000000000python-socks-2.7.1/python_socks/_protocols/errors.py000066400000000000000000000002261474746205100227700ustar00rootroot00000000000000class ReplyError(Exception): def __init__(self, message, error_code=None): super().__init__(message) self.error_code = error_code python-socks-2.7.1/python_socks/_protocols/http.py000066400000000000000000000106231474746205100224350ustar00rootroot00000000000000import sys from dataclasses import dataclass import base64 import binascii from collections import namedtuple from typing import Optional from .._version import __title__, __version__ from .errors import ReplyError DEFAULT_USER_AGENT = 'Python/{0[0]}.{0[1]} {1}/{2}'.format( sys.version_info, __title__, __version__, ) CRLF = '\r\n' class BasicAuth(namedtuple('BasicAuth', ['login', 'password', 'encoding'])): """Http basic authentication helper.""" def __new__(cls, login: str, password: str = '', encoding: str = 'latin1') -> 'BasicAuth': if login is None: raise ValueError('None is not allowed as login value') if password is None: raise ValueError('None is not allowed as password value') if ':' in login: raise ValueError('A ":" is not allowed in login (RFC 1945#section-11.1)') # noinspection PyTypeChecker,PyArgumentList return super().__new__(cls, login, password, encoding) @classmethod def decode(cls, auth_header: str, encoding: str = 'latin1') -> 'BasicAuth': """Create a BasicAuth object from an Authorization HTTP header.""" try: auth_type, encoded_credentials = auth_header.split(' ', 1) except ValueError: raise ValueError('Could not parse authorization header.') if auth_type.lower() != 'basic': raise ValueError('Unknown authorization method %s' % auth_type) try: decoded = base64.b64decode(encoded_credentials.encode('ascii'), validate=True).decode( encoding ) except binascii.Error: raise ValueError('Invalid base64 encoding.') try: # RFC 2617 HTTP Authentication # https://www.ietf.org/rfc/rfc2617.txt # the colon must be present, but the username and password may be # otherwise blank. username, password = decoded.split(':', 1) except ValueError: raise ValueError('Invalid credentials.') # noinspection PyTypeChecker return cls(username, password, encoding=encoding) def encode(self) -> str: """Encode credentials.""" creds = ('%s:%s' % (self.login, self.password)).encode(self.encoding) return 'Basic %s' % base64.b64encode(creds).decode(self.encoding) class _Buffer: def __init__(self, encoding: str = 'utf-8'): self._encoding = encoding self._buffer = bytearray() def append_line(self, line: str = ""): if line: self._buffer.extend(line.encode(self._encoding)) self._buffer.extend(CRLF.encode('ascii')) def dumps(self) -> bytes: return bytes(self._buffer) @dataclass class ConnectRequest: host: str port: int username: Optional[str] password: Optional[str] def dumps(self) -> bytes: buff = _Buffer() buff.append_line(f'CONNECT {self.host}:{self.port} HTTP/1.1') buff.append_line(f'Host: {self.host}:{self.port}') buff.append_line(f'User-Agent: {DEFAULT_USER_AGENT}') if self.username and self.password: auth = BasicAuth(self.username, self.password) buff.append_line(f'Proxy-Authorization: {auth.encode()}') buff.append_line() return buff.dumps() @dataclass class ConnectReply: status_code: int message: str @classmethod def loads(cls, data: bytes) -> 'ConnectReply': if not data: raise ReplyError('Invalid proxy response') # pragma: no cover line = data.split(CRLF.encode('ascii'), 1)[0] line = line.decode('utf-8', 'surrogateescape') try: version, code, *reason = line.split() except ValueError: # pragma: no cover raise ReplyError(f'Invalid status line: {line}') try: status_code = int(code) except ValueError: # pragma: no cover raise ReplyError(f'Invalid status code: {code}') status_message = " ".join(reason) if status_code != 200: msg = f'{status_code} {status_message}' raise ReplyError(msg, error_code=status_code) return cls(status_code=status_code, message=status_message) # noinspection PyMethodMayBeStatic class Connection: def send(self, request: ConnectRequest) -> bytes: return request.dumps() def receive(self, data: bytes) -> ConnectReply: return ConnectReply.loads(data) python-socks-2.7.1/python_socks/_protocols/socks4.py000066400000000000000000000057651474746205100226770ustar00rootroot00000000000000import enum import ipaddress import socket from dataclasses import dataclass from typing import Optional from .errors import ReplyError from .._helpers import is_ipv4_address RSV = NULL = 0x00 SOCKS_VER = 0x04 class Command(enum.IntEnum): CONNECT = 0x01 BIND = 0x02 class ReplyCode(enum.IntEnum): REQUEST_GRANTED = 0x5A REQUEST_REJECTED_OR_FAILED = 0x5B CONNECTION_FAILED = 0x5C AUTHENTICATION_FAILED = 0x5D ReplyMessages = { ReplyCode.REQUEST_GRANTED: 'Request granted', ReplyCode.REQUEST_REJECTED_OR_FAILED: 'Request rejected or failed', ReplyCode.CONNECTION_FAILED: ( 'Request rejected because SOCKS server cannot connect to identd on the client' ), ReplyCode.AUTHENTICATION_FAILED: ( 'Request rejected because the client program and identd report different user-ids' ), } @dataclass class ConnectRequest: host: str # hostname or IPv4 address port: int user_id: Optional[str] def dumps(self): port_bytes = self.port.to_bytes(2, 'big') include_hostname = False if is_ipv4_address(self.host): host_bytes = ipaddress.IPv4Address(self.host).packed else: include_hostname = True host_bytes = bytes([NULL, NULL, NULL, 1]) data = bytearray([SOCKS_VER, Command.CONNECT]) data += port_bytes data += host_bytes if self.user_id: data += self.user_id.encode('ascii') data.append(NULL) if include_hostname: data += self.host.encode('idna') data.append(NULL) return bytes(data) @dataclass class ConnectReply: SIZE = 8 rsv: int reply: ReplyCode host: str # should be ignored when using Command.CONNECT port: int # should be ignored when using Command.CONNECT @classmethod def loads(cls, data: bytes) -> 'ConnectReply': if len(data) != cls.SIZE: raise ReplyError('Malformed connect reply') rsv = data[0] if rsv != RSV: # pragma: no cover raise ReplyError(f'Unexpected reply version: {data[0]:#02X}') try: reply = ReplyCode(data[1]) except ValueError: raise ReplyError(f'Invalid reply code: {data[1]:#02X}') if reply != ReplyCode.REQUEST_GRANTED: # pragma: no cover msg = ReplyMessages.get(reply, 'Unknown error') raise ReplyError(msg, error_code=reply) try: port = int.from_bytes(data[2:4], byteorder="big") except ValueError: raise ReplyError('Invalid port data') try: host = socket.inet_ntop(socket.AF_INET, data[4:8]) except ValueError: raise ReplyError('Invalid port data') return cls(rsv=rsv, reply=reply, host=host, port=port) # noinspection PyMethodMayBeStatic class Connection: def send(self, request: ConnectRequest) -> bytes: return request.dumps() def receive(self, data: bytes) -> ConnectReply: return ConnectReply.loads(data) python-socks-2.7.1/python_socks/_protocols/socks5.py000066400000000000000000000227611474746205100226730ustar00rootroot00000000000000import enum import ipaddress import socket from typing import Optional, Union from dataclasses import dataclass, field from .errors import ReplyError from .._helpers import is_ip_address RSV = NULL = AUTH_GRANTED = 0x00 SOCKS_VER = 0x05 class AuthMethod(enum.IntEnum): ANONYMOUS = 0x00 GSSAPI = 0x01 USERNAME_PASSWORD = 0x02 NO_ACCEPTABLE = 0xFF class AddressType(enum.IntEnum): IPV4 = 0x01 DOMAIN = 0x03 IPV6 = 0x04 @classmethod def from_ip_ver(cls, ver: int): if ver == 4: return cls.IPV4 if ver == 6: return cls.IPV6 raise ValueError('Invalid IP version') class Command(enum.IntEnum): CONNECT = 0x01 BIND = 0x02 UDP_ASSOCIATE = 0x03 class ReplyCode(enum.IntEnum): SUCCEEDED = 0x00 GENERAL_FAILURE = 0x01 CONNECTION_NOT_ALLOWED = 0x02 NETWORK_UNREACHABLE = 0x03 HOST_UNREACHABLE = 0x04 CONNECTION_REFUSED = 0x05 TTL_EXPIRED = 0x06 COMMAND_NOT_SUPPORTED = 0x07 ADDRESS_TYPE_NOT_SUPPORTED = 0x08 ReplyMessages = { ReplyCode.SUCCEEDED: 'Request granted', ReplyCode.GENERAL_FAILURE: 'General SOCKS server failure', ReplyCode.CONNECTION_NOT_ALLOWED: 'Connection not allowed by ruleset', ReplyCode.NETWORK_UNREACHABLE: 'Network unreachable', ReplyCode.HOST_UNREACHABLE: 'Host unreachable', ReplyCode.CONNECTION_REFUSED: 'Connection refused by destination host', ReplyCode.TTL_EXPIRED: 'TTL expired', ReplyCode.COMMAND_NOT_SUPPORTED: 'Command not supported or protocol error', ReplyCode.ADDRESS_TYPE_NOT_SUPPORTED: 'Address type not supported', } @dataclass class AuthMethodsRequest: username: Optional[str] password: Optional[str] methods: bytearray = field(init=False) def __post_init__(self): methods = bytearray([AuthMethod.ANONYMOUS]) if self.username and self.password: methods.append(AuthMethod.USERNAME_PASSWORD) self.methods = methods def dumps(self) -> bytes: return bytes([SOCKS_VER, len(self.methods)]) + self.methods @dataclass class AuthMethodReply: SIZE = 2 ver: int method: AuthMethod def validate(self, request: AuthMethodsRequest): if self.method not in request.methods: # pragma: no cover raise ReplyError(f'Unexpected SOCKS authentication method: {self.method}') @classmethod def loads(cls, data: bytes) -> 'AuthMethodReply': if len(data) != cls.SIZE: raise ReplyError('Malformed authentication method reply') ver = data[0] if ver != SOCKS_VER: # pragma: no cover raise ReplyError(f'Unexpected SOCKS version number: {ver}') try: method = AuthMethod(data[1]) except ValueError: raise ReplyError(f'Invalid authentication method: {data[1]:#02X}') if method == AuthMethod.NO_ACCEPTABLE: # pragma: no cover raise ReplyError('No acceptable authentication methods were offered') return cls(ver=ver, method=method) @dataclass class AuthRequest: VER = 0x01 username: str password: str def dumps(self) -> bytes: data = bytearray() data.append(self.VER) data.append(len(self.username)) data += self.username.encode('ascii') data.append(len(self.password)) data += self.password.encode('ascii') return bytes(data) @dataclass class AuthReply: SIZE = 2 ver: int status: int @classmethod def loads(cls, data: bytes) -> 'AuthReply': if len(data) != cls.SIZE: raise ReplyError('Malformed auth reply') ver = data[0] if ver != AuthRequest.VER: # pragma: no cover raise ReplyError('Invalid authentication response') status = data[1] if status != AUTH_GRANTED: # pragma: no cover raise ReplyError('Username and password authentication failure') return cls(ver=ver, status=status) @dataclass class ConnectRequest: host: str # hostname or IPv4 or IPv6 address port: int def dumps(self) -> bytes: data = bytearray([SOCKS_VER, Command.CONNECT, RSV]) data += self._build_addr_request() return bytes(data) def _build_addr_request(self) -> bytes: port = self.port.to_bytes(2, 'big') if is_ip_address(self.host): ip = ipaddress.ip_address(self.host) address_type = AddressType.from_ip_ver(ip.version) return bytes([address_type]) + ip.packed + port else: address_type = AddressType.DOMAIN host = self.host.encode('idna') return bytes([address_type, len(host)]) + host + port @dataclass class ConnectReply: ver: int reply: ReplyCode rsv: int bound_host: str bound_port: int def validate(self): pass @classmethod def loads(cls, data: bytes) -> 'ConnectReply': if not data: raise ReplyError('Empty connect reply') ver = data[0] if ver != SOCKS_VER: # pragma: no cover raise ReplyError(f'Unexpected SOCKS version number: {ver:#02X}') try: reply = ReplyCode(data[1]) except IndexError: raise ReplyError('Malformed connect reply') except ValueError: raise ReplyError(f'Invalid reply code: {data[1]:#02X}') if reply != ReplyCode.SUCCEEDED: # pragma: no cover msg = ReplyMessages.get(reply, 'Unknown error') # type: ignore raise ReplyError(msg, error_code=reply) try: rsv = data[2] except IndexError: raise ReplyError('Malformed connect reply') if rsv != RSV: # pragma: no cover raise ReplyError(f'The reserved byte must be {RSV:#02X}') try: addr_type = data[3] bnd_host_data = data[4:-2] bnd_port_data = data[-2:] except IndexError: raise ReplyError('Malformed connect reply') if addr_type == AddressType.IPV4: bnd_host = socket.inet_ntop(socket.AF_INET, bnd_host_data) elif addr_type == AddressType.IPV6: bnd_host = socket.inet_ntop(socket.AF_INET6, bnd_host_data) elif addr_type == AddressType.DOMAIN: # pragma: no cover # host_len = bnd_host_data[0] bnd_host = bnd_host_data[1:].decode() else: # pragma: no cover raise ReplyError(f'Invalid address type: {addr_type:#02X}') bnd_port = int.from_bytes(bnd_port_data, 'big') return cls( ver=ver, reply=reply, rsv=rsv, bound_host=bnd_host, bound_port=bnd_port, ) class StateServerWaitingForAuthMethods: pass @dataclass class StateClientSentAuthMethods: data: AuthMethodsRequest @dataclass class StateServerWaitingForAuth: data: AuthMethodReply @dataclass class StateClientAuthenticated: data: Optional[AuthReply] = None @dataclass class StateClientSentAuthRequest: data: AuthRequest @dataclass class StateClientSentConnectRequest: data: ConnectRequest @dataclass class StateServerConnected: data: ConnectReply Request = Union[ AuthMethodsRequest, AuthRequest, ConnectRequest, ] Reply = Union[ AuthMethodReply, AuthReply, ConnectReply, ] ConnectionState = Union[ StateServerWaitingForAuthMethods, StateClientSentAuthMethods, StateServerWaitingForAuth, StateClientSentAuthRequest, StateClientAuthenticated, StateClientSentConnectRequest, StateServerConnected, ] class Connection: _state: ConnectionState def __init__(self): self._state = StateServerWaitingForAuthMethods() def send(self, request: Request) -> bytes: if type(request) is AuthMethodsRequest: if type(self._state) is not StateServerWaitingForAuthMethods: raise RuntimeError('Server is not currently waiting for auth methods') self._state = StateClientSentAuthMethods(request) return request.dumps() if type(request) is AuthRequest: if type(self._state) is not StateServerWaitingForAuth: raise RuntimeError('Server is not currently waiting for authentication') self._state = StateClientSentAuthRequest(request) return request.dumps() if type(request) is ConnectRequest: if type(self._state) is not StateClientAuthenticated: raise RuntimeError('Client is not authenticated') self._state = StateClientSentConnectRequest(request) return request.dumps() raise RuntimeError(f'Invalid request type: {type(request)}') def receive(self, data: bytes) -> Reply: if type(self._state) is StateClientSentAuthMethods: reply = AuthMethodReply.loads(data) reply.validate(self._state.data) if reply.method == AuthMethod.USERNAME_PASSWORD: self._state = StateServerWaitingForAuth(data=reply) else: self._state = StateClientAuthenticated() return reply if type(self._state) is StateClientSentAuthRequest: reply = AuthReply.loads(data) self._state = StateClientAuthenticated(data=reply) return reply if type(self._state) is StateClientSentConnectRequest: reply = ConnectReply.loads(data) self._state = StateServerConnected(data=reply) return reply raise RuntimeError(f'Invalid connection state: {self._state}') @property def state(self): return self._state python-socks-2.7.1/python_socks/_types.py000066400000000000000000000001321474746205100205700ustar00rootroot00000000000000from enum import Enum class ProxyType(Enum): SOCKS4 = 1 SOCKS5 = 2 HTTP = 3 python-socks-2.7.1/python_socks/_version.py000066400000000000000000000000611474746205100211120ustar00rootroot00000000000000__title__ = 'python-socks' __version__ = '2.7.1' python-socks-2.7.1/python_socks/async_/000077500000000000000000000000001474746205100201735ustar00rootroot00000000000000python-socks-2.7.1/python_socks/async_/__init__.py000066400000000000000000000001001474746205100222730ustar00rootroot00000000000000from ._proxy_chain import ProxyChain __all__ = ('ProxyChain',) python-socks-2.7.1/python_socks/async_/_proxy_chain.py000066400000000000000000000017331474746205100232330ustar00rootroot00000000000000from typing import Iterable import warnings class ProxyChain: def __init__(self, proxies: Iterable): warnings.warn( 'This implementation of ProxyChain is deprecated and will be removed in the future', DeprecationWarning, stacklevel=2, ) self._proxies = proxies async def connect(self, dest_host, dest_port, timeout=None): curr_socket = None proxies = list(self._proxies) length = len(proxies) - 1 for i in range(length): curr_socket = await proxies[i].connect( dest_host=proxies[i + 1].proxy_host, dest_port=proxies[i + 1].proxy_port, timeout=timeout, _socket=curr_socket, ) curr_socket = await proxies[length].connect( dest_host=dest_host, dest_port=dest_port, timeout=timeout, _socket=curr_socket, ) return curr_socket python-socks-2.7.1/python_socks/async_/anyio/000077500000000000000000000000001474746205100213125ustar00rootroot00000000000000python-socks-2.7.1/python_socks/async_/anyio/__init__.py000066400000000000000000000001521474746205100234210ustar00rootroot00000000000000from ._proxy import AnyioProxy as Proxy from ._chain import ProxyChain __all__ = ('Proxy', 'ProxyChain') python-socks-2.7.1/python_socks/async_/anyio/_chain.py000066400000000000000000000021131474746205100231020ustar00rootroot00000000000000from typing import Iterable import warnings from ._proxy import AnyioProxy class ProxyChain: def __init__(self, proxies: Iterable[AnyioProxy]): warnings.warn( 'This implementation of ProxyChain is deprecated and will be removed in the future', DeprecationWarning, stacklevel=2, ) self._proxies = proxies async def connect( self, dest_host, dest_port, dest_ssl=None, timeout=None, ): _stream = None proxies = list(self._proxies) length = len(proxies) - 1 for i in range(length): _stream = await proxies[i].connect( dest_host=proxies[i + 1].proxy_host, dest_port=proxies[i + 1].proxy_port, timeout=timeout, _stream=_stream, ) _stream = await proxies[length].connect( dest_host=dest_host, dest_port=dest_port, dest_ssl=dest_ssl, timeout=timeout, _stream=_stream, ) return _stream python-socks-2.7.1/python_socks/async_/anyio/_connect.py000066400000000000000000000004621474746205100234560ustar00rootroot00000000000000from typing import Optional import anyio import anyio.abc async def connect_tcp( host: str, port: int, local_host: Optional[str] = None, ) -> anyio.abc.SocketStream: return await anyio.connect_tcp( remote_host=host, remote_port=port, local_host=local_host, ) python-socks-2.7.1/python_socks/async_/anyio/_proxy.py000066400000000000000000000103421474746205100232040ustar00rootroot00000000000000import ssl from typing import Any, Optional import warnings import anyio from ..._types import ProxyType from ..._helpers import parse_proxy_url from ..._errors import ProxyConnectionError, ProxyTimeoutError, ProxyError from ._resolver import Resolver from ._stream import AnyioSocketStream from ._connect import connect_tcp from ..._protocols.errors import ReplyError from ..._connectors.factory_async import create_connector DEFAULT_TIMEOUT = 60 class AnyioProxy: _stream: Optional[AnyioSocketStream] def __init__( self, proxy_type: ProxyType, host: str, port: int, username: Optional[str] = None, password: Optional[str] = None, rdns: Optional[bool] = None, proxy_ssl: Optional[ssl.SSLContext] = None, ): self._proxy_type = proxy_type self._proxy_host = host self._proxy_port = port self._password = password self._username = username self._rdns = rdns self._proxy_ssl = proxy_ssl self._resolver = Resolver() async def connect( self, dest_host: str, dest_port: int, dest_ssl: Optional[ssl.SSLContext] = None, timeout: Optional[float] = None, **kwargs: Any, ) -> AnyioSocketStream: if timeout is None: timeout = DEFAULT_TIMEOUT _stream = kwargs.get('_stream') if _stream is not None: warnings.warn( "The '_stream' argument is deprecated and will be removed in the future", DeprecationWarning, stacklevel=2, ) local_host = kwargs.get('local_host') try: with anyio.fail_after(timeout): if _stream is None: try: _stream = AnyioSocketStream( await connect_tcp( host=self._proxy_host, port=self._proxy_port, local_host=local_host, ) ) except OSError as e: msg = 'Could not connect to proxy {}:{} [{}]'.format( self._proxy_host, self._proxy_port, e.strerror, ) raise ProxyConnectionError(e.errno, msg) from e stream = _stream try: if self._proxy_ssl is not None: stream = await stream.start_tls( hostname=self._proxy_host, ssl_context=self._proxy_ssl, ) connector = create_connector( proxy_type=self._proxy_type, username=self._username, password=self._password, rdns=self._rdns, resolver=self._resolver, ) await connector.connect( stream=stream, host=dest_host, port=dest_port, ) if dest_ssl is not None: stream = await stream.start_tls( hostname=dest_host, ssl_context=dest_ssl, ) return stream except ReplyError as e: await stream.close() raise ProxyError(e, error_code=e.error_code) except BaseException: await stream.close() raise except TimeoutError as e: raise ProxyTimeoutError(f'Proxy connection timed out: {timeout}') from e @property def proxy_host(self): return self._proxy_host @property def proxy_port(self): return self._proxy_port @classmethod def create(cls, *args, **kwargs): # for backward compatibility return cls(*args, **kwargs) @classmethod def from_url(cls, url: str, **kwargs) -> 'AnyioProxy': url_args = parse_proxy_url(url) return cls(*url_args, **kwargs) python-socks-2.7.1/python_socks/async_/anyio/_resolver.py000066400000000000000000000011161474746205100236630ustar00rootroot00000000000000import anyio import socket from ... import _abc as abc class Resolver(abc.AsyncResolver): async def resolve(self, host, port=0, family=socket.AF_UNSPEC): infos = await anyio.getaddrinfo( host=host, port=port, family=family, type=socket.SOCK_STREAM, ) if not infos: # pragma: no cover raise OSError('Can`t resolve address {}:{} [{}]'.format(host, port, family)) infos = sorted(infos, key=lambda info: info[0]) family, _, _, _, address = infos[0] return family, address[0] python-socks-2.7.1/python_socks/async_/anyio/_stream.py000066400000000000000000000031161474746205100233170ustar00rootroot00000000000000import ssl from typing import Union import anyio import anyio.abc from anyio.streams.tls import TLSStream from ..._errors import ProxyError from ... import _abc as abc DEFAULT_RECEIVE_SIZE = 65536 AnyioStreamType = Union[anyio.abc.SocketStream, TLSStream] class AnyioSocketStream(abc.AsyncSocketStream): _stream: AnyioStreamType def __init__(self, stream: AnyioStreamType) -> None: self._stream = stream async def write_all(self, data: bytes): await self._stream.send(item=data) async def read(self, max_bytes: int = DEFAULT_RECEIVE_SIZE): try: return await self._stream.receive(max_bytes=max_bytes) except anyio.EndOfStream: # pragma: no cover return b"" async def read_exact(self, n: int): data = bytearray() while len(data) < n: packet = await self.read(n - len(data)) if not packet: # pragma: no cover raise ProxyError('Connection closed unexpectedly') data += packet return data async def start_tls( self, hostname: str, ssl_context: ssl.SSLContext, ) -> 'AnyioSocketStream': ssl_stream = await TLSStream.wrap( self._stream, ssl_context=ssl_context, hostname=hostname, standard_compatible=False, server_side=False, ) return AnyioSocketStream(ssl_stream) async def close(self): await self._stream.aclose() @property def anyio_stream(self) -> AnyioStreamType: # pragma: no cover return self._stream python-socks-2.7.1/python_socks/async_/anyio/v2/000077500000000000000000000000001474746205100216415ustar00rootroot00000000000000python-socks-2.7.1/python_socks/async_/anyio/v2/__init__.py000066400000000000000000000001651474746205100237540ustar00rootroot00000000000000from ._proxy import AnyioProxy as Proxy from ._chain import ProxyChain __all__ = ( 'Proxy', 'ProxyChain', ) python-socks-2.7.1/python_socks/async_/anyio/v2/_chain.py000066400000000000000000000014351474746205100234370ustar00rootroot00000000000000from typing import Sequence import warnings from ._proxy import AnyioProxy class ProxyChain: def __init__(self, proxies: Sequence[AnyioProxy]): warnings.warn( 'This implementation of ProxyChain is deprecated and will be removed in the future', DeprecationWarning, stacklevel=2, ) self._proxies = proxies async def connect( self, dest_host, dest_port, dest_ssl=None, timeout=None, ): forward = None for proxy in self._proxies: proxy._forward = forward forward = proxy return await forward.connect( dest_host=dest_host, dest_port=dest_port, dest_ssl=dest_ssl, timeout=timeout, ) python-socks-2.7.1/python_socks/async_/anyio/v2/_connect.py000066400000000000000000000005601474746205100240040ustar00rootroot00000000000000from typing import Optional import anyio import anyio.abc from ._stream import AnyioSocketStream async def connect_tcp( host: str, port: int, local_host: Optional[str] = None, ) -> AnyioSocketStream: s = await anyio.connect_tcp( remote_host=host, remote_port=port, local_host=local_host, ) return AnyioSocketStream(s) python-socks-2.7.1/python_socks/async_/anyio/v2/_proxy.py000066400000000000000000000077651474746205100235520ustar00rootroot00000000000000import ssl from typing import Any, Optional import anyio from ._connect import connect_tcp from ._stream import AnyioSocketStream from .._resolver import Resolver from ...._errors import ProxyConnectionError, ProxyTimeoutError, ProxyError from ...._types import ProxyType from ...._helpers import parse_proxy_url from ...._protocols.errors import ReplyError from ...._connectors.factory_async import create_connector DEFAULT_TIMEOUT = 60 class AnyioProxy: def __init__( self, proxy_type: ProxyType, host: str, port: int, username: Optional[str] = None, password: Optional[str] = None, rdns: Optional[bool] = None, proxy_ssl: Optional[ssl.SSLContext] = None, forward: Optional['AnyioProxy'] = None, ): self._proxy_type = proxy_type self._proxy_host = host self._proxy_port = port self._username = username self._password = password self._rdns = rdns self._proxy_ssl = proxy_ssl self._forward = forward self._resolver = Resolver() async def connect( self, dest_host: str, dest_port: int, dest_ssl: Optional[ssl.SSLContext] = None, timeout: Optional[float] = None, **kwargs: Any, ) -> AnyioSocketStream: if timeout is None: timeout = DEFAULT_TIMEOUT local_host = kwargs.get('local_host') try: with anyio.fail_after(timeout): return await self._connect( dest_host=dest_host, dest_port=dest_port, dest_ssl=dest_ssl, local_host=local_host, ) except TimeoutError as e: raise ProxyTimeoutError('Proxy connection timed out: {}'.format(timeout)) from e async def _connect( self, dest_host: str, dest_port: int, dest_ssl: Optional[ssl.SSLContext] = None, local_host: Optional[str] = None, ) -> AnyioSocketStream: if self._forward is None: try: stream = await connect_tcp( host=self._proxy_host, port=self._proxy_port, local_host=local_host, ) except OSError as e: raise ProxyConnectionError( e.errno, "Couldn't connect to proxy" f" {self._proxy_host}:{self._proxy_port} [{e.strerror}]", ) from e else: stream = await self._forward.connect( dest_host=self._proxy_host, dest_port=self._proxy_port, ) try: if self._proxy_ssl is not None: stream = await stream.start_tls( hostname=self._proxy_host, ssl_context=self._proxy_ssl, ) connector = create_connector( proxy_type=self._proxy_type, username=self._username, password=self._password, rdns=self._rdns, resolver=self._resolver, ) await connector.connect( stream=stream, host=dest_host, port=dest_port, ) if dest_ssl is not None: stream = await stream.start_tls( hostname=dest_host, ssl_context=dest_ssl, ) except ReplyError as e: await stream.close() raise ProxyError(e, error_code=e.error_code) except BaseException: with anyio.CancelScope(shield=True): await stream.close() raise return stream @classmethod def create(cls, *args, **kwargs): # for backward compatibility return cls(*args, **kwargs) @classmethod def from_url(cls, url: str, **kwargs) -> 'AnyioProxy': url_args = parse_proxy_url(url) return cls(*url_args, **kwargs) python-socks-2.7.1/python_socks/async_/anyio/v2/_stream.py000066400000000000000000000031201474746205100236410ustar00rootroot00000000000000import ssl from typing import Union import anyio import anyio.abc from anyio.streams.tls import TLSStream from ...._errors import ProxyError from .... import _abc as abc DEFAULT_RECEIVE_SIZE = 65536 AnyioStreamType = Union[anyio.abc.SocketStream, TLSStream] class AnyioSocketStream(abc.AsyncSocketStream): _stream: AnyioStreamType def __init__(self, stream: AnyioStreamType) -> None: self._stream = stream async def write_all(self, data: bytes): await self._stream.send(item=data) async def read(self, max_bytes: int = DEFAULT_RECEIVE_SIZE): try: return await self._stream.receive(max_bytes=max_bytes) except anyio.EndOfStream: # pragma: no cover return b"" async def read_exact(self, n: int): data = bytearray() while len(data) < n: packet = await self.read(n - len(data)) if not packet: # pragma: no cover raise ProxyError('Connection closed unexpectedly') data += packet return data async def start_tls( self, hostname: str, ssl_context: ssl.SSLContext, ) -> 'AnyioSocketStream': ssl_stream = await TLSStream.wrap( self._stream, ssl_context=ssl_context, hostname=hostname, standard_compatible=False, server_side=False, ) return AnyioSocketStream(ssl_stream) async def close(self): await self._stream.aclose() @property def anyio_stream(self) -> AnyioStreamType: # pragma: no cover return self._stream python-socks-2.7.1/python_socks/async_/asyncio/000077500000000000000000000000001474746205100216405ustar00rootroot00000000000000python-socks-2.7.1/python_socks/async_/asyncio/__init__.py000066400000000000000000000001011474746205100237410ustar00rootroot00000000000000from ._proxy import AsyncioProxy as Proxy __all__ = ('Proxy',) python-socks-2.7.1/python_socks/async_/asyncio/_connect.py000066400000000000000000000021261474746205100240030ustar00rootroot00000000000000import socket import asyncio from typing import Optional, Tuple from ._resolver import Resolver from ..._helpers import is_ipv4_address, is_ipv6_address async def connect_tcp( host: str, port: int, loop: asyncio.AbstractEventLoop, local_addr: Optional[Tuple[str, int]] = None, ) -> socket.socket: family, host = await _resolve_host(host, loop) sock = socket.socket(family=family, type=socket.SOCK_STREAM) sock.setblocking(False) if local_addr is not None: # pragma: no cover sock.bind(local_addr) if is_ipv6_address(host): address = (host, port, 0, 0) # to fix OSError: [WinError 10022] else: address = (host, port) # type: ignore[assignment] try: await loop.sock_connect(sock=sock, address=address) except OSError: sock.close() raise return sock async def _resolve_host(host, loop): if is_ipv4_address(host): return socket.AF_INET, host if is_ipv6_address(host): return socket.AF_INET6, host resolver = Resolver(loop=loop) return await resolver.resolve(host=host) python-socks-2.7.1/python_socks/async_/asyncio/_proxy.py000066400000000000000000000100301474746205100235240ustar00rootroot00000000000000import asyncio import socket import sys from typing import Any, Optional import warnings from ..._types import ProxyType from ..._helpers import parse_proxy_url from ..._errors import ProxyConnectionError, ProxyTimeoutError, ProxyError from ._stream import AsyncioSocketStream from ._resolver import Resolver from ..._protocols.errors import ReplyError from ..._connectors.factory_async import create_connector from ._connect import connect_tcp if sys.version_info >= (3, 11): import asyncio as async_timeout # pylint:disable=reimported else: import async_timeout DEFAULT_TIMEOUT = 60 class AsyncioProxy: def __init__( self, proxy_type: ProxyType, host: str, port: int, username: Optional[str] = None, password: Optional[str] = None, rdns: Optional[bool] = None, loop: Optional[asyncio.AbstractEventLoop] = None, ): if loop is None: loop = asyncio.get_event_loop() self._loop = loop self._proxy_type = proxy_type self._proxy_host = host self._proxy_port = port self._password = password self._username = username self._rdns = rdns self._resolver = Resolver(loop=loop) async def connect( self, dest_host: str, dest_port: int, timeout: Optional[float] = None, **kwargs: Any, ) -> socket.socket: if timeout is None: timeout = DEFAULT_TIMEOUT _socket = kwargs.get('_socket') if _socket is not None: warnings.warn( "The '_socket' argument is deprecated and will be removed in the future", DeprecationWarning, stacklevel=2, ) local_addr = kwargs.get('local_addr') try: async with async_timeout.timeout(timeout): return await self._connect( dest_host=dest_host, dest_port=dest_port, _socket=_socket, local_addr=local_addr, ) except asyncio.TimeoutError as e: raise ProxyTimeoutError(f'Proxy connection timed out: {timeout}') from e async def _connect( self, dest_host, dest_port, _socket=None, local_addr=None, ) -> socket.socket: if _socket is None: try: _socket = await connect_tcp( host=self._proxy_host, port=self._proxy_port, loop=self._loop, local_addr=local_addr, ) except OSError as e: msg = 'Could not connect to proxy {}:{} [{}]'.format( self._proxy_host, self._proxy_port, e.strerror, ) raise ProxyConnectionError(e.errno, msg) from e stream = AsyncioSocketStream(sock=_socket, loop=self._loop) try: connector = create_connector( proxy_type=self._proxy_type, username=self._username, password=self._password, rdns=self._rdns, resolver=self._resolver, ) await connector.connect( stream=stream, host=dest_host, port=dest_port, ) return _socket except ReplyError as e: await stream.close() raise ProxyError(e, error_code=e.error_code) except (asyncio.CancelledError, Exception): # pragma: no cover await stream.close() raise @property def proxy_host(self): return self._proxy_host @property def proxy_port(self): return self._proxy_port @classmethod def create(cls, *args, **kwargs): # for backward compatibility return cls(*args, **kwargs) @classmethod def from_url(cls, url: str, **kwargs) -> 'AsyncioProxy': url_args = parse_proxy_url(url) return cls(*url_args, **kwargs) python-socks-2.7.1/python_socks/async_/asyncio/_resolver.py000066400000000000000000000012511474746205100242110ustar00rootroot00000000000000import asyncio import socket from ... import _abc as abc class Resolver(abc.AsyncResolver): def __init__(self, loop: asyncio.AbstractEventLoop): self._loop = loop async def resolve(self, host, port=0, family=socket.AF_UNSPEC): infos = await self._loop.getaddrinfo( host=host, port=port, family=family, type=socket.SOCK_STREAM, ) if not infos: # pragma: no cover raise OSError('Can`t resolve address {}:{} [{}]'.format(host, port, family)) infos = sorted(infos, key=lambda info: info[0]) family, _, _, _, address = infos[0] return family, address[0] python-socks-2.7.1/python_socks/async_/asyncio/_stream.py000066400000000000000000000017751474746205100236560ustar00rootroot00000000000000import asyncio import socket from ..._errors import ProxyError from ... import _abc as abc DEFAULT_RECEIVE_SIZE = 65536 class AsyncioSocketStream(abc.AsyncSocketStream): _loop: asyncio.AbstractEventLoop = None _socket = None def __init__(self, sock: socket.socket, loop: asyncio.AbstractEventLoop): self._loop = loop self._socket = sock async def write_all(self, data): await self._loop.sock_sendall(self._socket, data) async def read(self, max_bytes=DEFAULT_RECEIVE_SIZE): return await self._loop.sock_recv(self._socket, max_bytes) async def read_exact(self, n): data = bytearray() while len(data) < n: packet = await self._loop.sock_recv(self._socket, n - len(data)) if not packet: # pragma: no cover raise ProxyError('Connection closed unexpectedly') data += packet return data async def close(self): if self._socket is not None: self._socket.close() python-socks-2.7.1/python_socks/async_/asyncio/v2/000077500000000000000000000000001474746205100221675ustar00rootroot00000000000000python-socks-2.7.1/python_socks/async_/asyncio/v2/__init__.py000066400000000000000000000001541474746205100243000ustar00rootroot00000000000000from ._proxy import AsyncioProxy as Proxy from ._chain import ProxyChain __all__ = ('Proxy', 'ProxyChain') python-socks-2.7.1/python_socks/async_/asyncio/v2/_chain.py000066400000000000000000000014531474746205100237650ustar00rootroot00000000000000from typing import Sequence import warnings from ._proxy import AsyncioProxy class ProxyChain: def __init__(self, proxies: Sequence[AsyncioProxy]): warnings.warn( 'This implementation of ProxyChain is deprecated and will be removed in the future', DeprecationWarning, stacklevel=2, ) self._proxies = proxies async def connect( self, dest_host: str, dest_port: int, dest_ssl=None, timeout=None, ): forward = None for proxy in self._proxies: proxy._forward = forward forward = proxy return await forward.connect( dest_host=dest_host, dest_port=dest_port, dest_ssl=dest_ssl, timeout=timeout, ) python-socks-2.7.1/python_socks/async_/asyncio/v2/_connect.py000066400000000000000000000011331474746205100243270ustar00rootroot00000000000000import asyncio from typing import Optional, Tuple from ._stream import AsyncioSocketStream async def connect_tcp( host: str, port: int, loop: asyncio.AbstractEventLoop, local_addr: Optional[Tuple[str, int]] = None, ) -> AsyncioSocketStream: kwargs = {} if local_addr is not None: kwargs['local_addr'] = local_addr # pragma: no cover reader, writer = await asyncio.open_connection( host=host, port=port, **kwargs, # type: ignore ) return AsyncioSocketStream( loop=loop, reader=reader, writer=writer, ) python-socks-2.7.1/python_socks/async_/asyncio/v2/_proxy.py000066400000000000000000000111521474746205100240610ustar00rootroot00000000000000import asyncio import ssl from typing import Any, Optional, Tuple import warnings import sys from ...._types import ProxyType from ...._helpers import parse_proxy_url from ...._errors import ProxyConnectionError, ProxyTimeoutError, ProxyError from ...._protocols.errors import ReplyError from ...._connectors.factory_async import create_connector from .._resolver import Resolver from ._stream import AsyncioSocketStream from ._connect import connect_tcp if sys.version_info >= (3, 11): import asyncio as async_timeout # pylint:disable=reimported else: import async_timeout DEFAULT_TIMEOUT = 60 class AsyncioProxy: def __init__( self, proxy_type: ProxyType, host: str, port: int, username: Optional[str] = None, password: Optional[str] = None, rdns: Optional[bool] = None, proxy_ssl: Optional[ssl.SSLContext] = None, forward: Optional['AsyncioProxy'] = None, loop: Optional[asyncio.AbstractEventLoop] = None, ): if loop is not None: # pragma: no cover warnings.warn( 'The loop argument is deprecated and scheduled for removal in the future.', DeprecationWarning, stacklevel=2, ) if loop is None: loop = asyncio.get_event_loop() self._loop = loop self._proxy_type = proxy_type self._proxy_host = host self._proxy_port = port self._username = username self._password = password self._rdns = rdns self._proxy_ssl = proxy_ssl self._forward = forward self._resolver = Resolver(loop=loop) async def connect( self, dest_host: str, dest_port: int, dest_ssl: Optional[ssl.SSLContext] = None, timeout: Optional[float] = None, **kwargs: Any, ) -> AsyncioSocketStream: if timeout is None: timeout = DEFAULT_TIMEOUT local_addr = kwargs.get('local_addr') try: async with async_timeout.timeout(timeout): return await self._connect( dest_host=dest_host, dest_port=dest_port, dest_ssl=dest_ssl, local_addr=local_addr, ) except asyncio.TimeoutError as e: raise ProxyTimeoutError('Proxy connection timed out: {}'.format(timeout)) from e async def _connect( self, dest_host: str, dest_port: int, dest_ssl: Optional[ssl.SSLContext] = None, local_addr: Optional[Tuple[str, int]] = None, ) -> AsyncioSocketStream: if self._forward is None: try: stream = await connect_tcp( host=self._proxy_host, port=self._proxy_port, loop=self._loop, local_addr=local_addr, ) except OSError as e: raise ProxyConnectionError( e.errno, "Couldn't connect to proxy" f" {self._proxy_host}:{self._proxy_port} [{e.strerror}]", ) from e else: stream = await self._forward.connect( dest_host=self._proxy_host, dest_port=self._proxy_port, ) try: if self._proxy_ssl is not None: stream = await stream.start_tls( hostname=self._proxy_host, ssl_context=self._proxy_ssl, ) connector = create_connector( proxy_type=self._proxy_type, username=self._username, password=self._password, rdns=self._rdns, resolver=self._resolver, ) await connector.connect( stream=stream, host=dest_host, port=dest_port, ) if dest_ssl is not None: stream = await stream.start_tls( hostname=dest_host, ssl_context=dest_ssl, ) except ReplyError as e: await stream.close() raise ProxyError(e, error_code=e.error_code) except (asyncio.CancelledError, Exception): await stream.close() raise return stream @classmethod def create(cls, *args, **kwargs): # for backward compatibility return cls(*args, **kwargs) @classmethod def from_url(cls, url: str, **kwargs) -> 'AsyncioProxy': url_args = parse_proxy_url(url) return cls(*url_args, **kwargs) python-socks-2.7.1/python_socks/async_/asyncio/v2/_stream.py000066400000000000000000000053011474746205100241720ustar00rootroot00000000000000import asyncio import ssl from .... import _abc as abc DEFAULT_RECEIVE_SIZE = 65536 class AsyncioSocketStream(abc.AsyncSocketStream): _loop: asyncio.AbstractEventLoop _reader: asyncio.StreamReader _writer: asyncio.StreamWriter def __init__( self, loop: asyncio.AbstractEventLoop, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, ): self._loop = loop self._reader = reader self._writer = writer async def write_all(self, data): self._writer.write(data) await self._writer.drain() async def read(self, max_bytes=DEFAULT_RECEIVE_SIZE): return await self._reader.read(max_bytes) async def read_exact(self, n): return await self._reader.readexactly(n) async def start_tls( self, hostname: str, ssl_context: ssl.SSLContext, ssl_handshake_timeout=None, ) -> 'AsyncioSocketStream': if hasattr(self._writer, 'start_tls'): # Python>=3.11 await self._writer.start_tls( ssl_context, server_hostname=hostname, ssl_handshake_timeout=ssl_handshake_timeout, ) return self reader = asyncio.StreamReader() protocol = asyncio.StreamReaderProtocol(reader) transport: asyncio.Transport = await self._loop.start_tls( self._writer.transport, # type: ignore protocol, ssl_context, server_side=False, server_hostname=hostname, ssl_handshake_timeout=ssl_handshake_timeout, ) # reader.set_transport(transport) # Initialize the protocol, so it is made aware of being tied to # a TLS connection. # See: https://github.com/encode/httpx/issues/859 protocol.connection_made(transport) writer = asyncio.StreamWriter( transport=transport, protocol=protocol, reader=reader, loop=self._loop, ) stream = AsyncioSocketStream(loop=self._loop, reader=reader, writer=writer) # When we return a new SocketStream with new StreamReader/StreamWriter instances # we need to keep references to the old StreamReader/StreamWriter so that they # are not garbage collected and closed while we're still using them. stream._inner = self # type: ignore # pylint:disable=W0212,W0201 return stream async def close(self): self._writer.close() self._writer.transport.abort() # noqa @property def reader(self): return self._reader # pragma: no cover @property def writer(self): return self._writer # pragma: no cover python-socks-2.7.1/python_socks/async_/curio/000077500000000000000000000000001474746205100213145ustar00rootroot00000000000000python-socks-2.7.1/python_socks/async_/curio/__init__.py000066400000000000000000000000771474746205100234310ustar00rootroot00000000000000from ._proxy import CurioProxy as Proxy __all__ = ('Proxy',) python-socks-2.7.1/python_socks/async_/curio/_connect.py000066400000000000000000000005101474746205100234520ustar00rootroot00000000000000from typing import Optional, Tuple import curio import curio.io import curio.socket async def connect_tcp( host: str, port: int, local_addr: Optional[Tuple[str, int]] = None, ) -> curio.io.Socket: return await curio.open_connection( host=host, port=port, source_addr=local_addr, ) python-socks-2.7.1/python_socks/async_/curio/_proxy.py000066400000000000000000000070741474746205100232160ustar00rootroot00000000000000from typing import Any, Optional import warnings import curio import curio.io from ..._types import ProxyType from ..._helpers import parse_proxy_url from ..._errors import ProxyConnectionError, ProxyTimeoutError, ProxyError from ._stream import CurioSocketStream from ._resolver import Resolver from ._connect import connect_tcp from ..._protocols.errors import ReplyError from ..._connectors.factory_async import create_connector DEFAULT_TIMEOUT = 60 class CurioProxy: def __init__( self, proxy_type: ProxyType, host: str, port: int, username: Optional[str] = None, password: Optional[str] = None, rdns: Optional[bool] = None, ): self._proxy_type = proxy_type self._proxy_host = host self._proxy_port = port self._password = password self._username = username self._rdns = rdns self._resolver = Resolver() async def connect( self, dest_host: str, dest_port: int, timeout: Optional[float] = None, **kwargs: Any, ) -> curio.io.Socket: if timeout is None: timeout = DEFAULT_TIMEOUT _socket = kwargs.get('_socket') if _socket is not None: warnings.warn( "The '_socket' argument is deprecated and will be removed in the future", DeprecationWarning, stacklevel=2, ) local_addr = kwargs.get('local_addr') try: return await curio.timeout_after( timeout, self._connect, dest_host, dest_port, _socket, local_addr, ) except curio.TaskTimeout as e: raise ProxyTimeoutError(f'Proxy connection timed out: {timeout}') from e async def _connect( self, dest_host: str, dest_port: int, _socket=None, local_addr=None, ): if _socket is None: try: _socket = await connect_tcp( host=self._proxy_host, port=self._proxy_port, local_addr=local_addr, ) except OSError as e: msg = 'Could not connect to proxy {}:{} [{}]'.format( self._proxy_host, self._proxy_port, e.strerror, ) raise ProxyConnectionError(e.errno, msg) from e stream = CurioSocketStream(_socket) try: connector = create_connector( proxy_type=self._proxy_type, username=self._username, password=self._password, rdns=self._rdns, resolver=self._resolver, ) await connector.connect( stream=stream, host=dest_host, port=dest_port, ) return _socket except ReplyError as e: await stream.close() raise ProxyError(e, error_code=e.error_code) except BaseException: await stream.close() raise @property def proxy_host(self): return self._proxy_host @property def proxy_port(self): return self._proxy_port @classmethod def create(cls, *args, **kwargs): # for backward compatibility return cls(*args, **kwargs) @classmethod def from_url(cls, url: str, **kwargs) -> 'CurioProxy': url_args = parse_proxy_url(url) return cls(*url_args, **kwargs) python-socks-2.7.1/python_socks/async_/curio/_resolver.py000066400000000000000000000013221474746205100236640ustar00rootroot00000000000000import socket from curio.socket import getaddrinfo from ... import _abc as abc class Resolver(abc.AsyncResolver): async def resolve(self, host, port=0, family=socket.AF_UNSPEC): try: infos = await getaddrinfo( host=host, port=port, family=family, type=socket.SOCK_STREAM, ) except socket.gaierror: # pragma: no cover infos = None if not infos: # pragma: no cover raise OSError('Can`t resolve address {}:{} [{}]'.format(host, port, family)) infos = sorted(infos, key=lambda info: info[0]) family, _, _, _, address = infos[0] return family, address[0] python-socks-2.7.1/python_socks/async_/curio/_stream.py000066400000000000000000000015271474746205100233250ustar00rootroot00000000000000import curio.io import curio.socket from ... import _abc as abc from ..._errors import ProxyError DEFAULT_RECEIVE_SIZE = 65536 class CurioSocketStream(abc.AsyncSocketStream): _socket: curio.io.Socket = None def __init__(self, sock: curio.io.Socket): self._socket = sock async def write_all(self, data): await self._socket.sendall(data) async def read(self, max_bytes=DEFAULT_RECEIVE_SIZE): return await self._socket.recv(max_bytes) async def read_exact(self, n): data = bytearray() while len(data) < n: packet = await self._socket.recv(n - len(data)) if not packet: # pragma: no cover raise ProxyError('Connection closed unexpectedly') data += packet return data async def close(self): await self._socket.close() python-socks-2.7.1/python_socks/async_/trio/000077500000000000000000000000001474746205100211505ustar00rootroot00000000000000python-socks-2.7.1/python_socks/async_/trio/__init__.py000066400000000000000000000000751474746205100232630ustar00rootroot00000000000000from ._proxy import TrioProxy as Proxy __all__ = ('Proxy',) python-socks-2.7.1/python_socks/async_/trio/_connect.py000066400000000000000000000014171474746205100233150ustar00rootroot00000000000000from typing import Optional, Tuple import trio from ._resolver import Resolver from ..._helpers import is_ipv4_address, is_ipv6_address async def connect_tcp( host: str, port: int, local_addr: Optional[Tuple[str, int]] = None, ) -> trio.socket.SocketType: family, host = await _resolve_host(host) sock = trio.socket.socket(family=family, type=trio.socket.SOCK_STREAM) if local_addr is not None: # pragma: no cover await sock.bind(local_addr) await sock.connect((host, port)) return sock async def _resolve_host(host): if is_ipv4_address(host): return trio.socket.AF_INET, host if is_ipv6_address(host): return trio.socket.AF_INET6, host resolver = Resolver() return await resolver.resolve(host=host) python-socks-2.7.1/python_socks/async_/trio/_proxy.py000066400000000000000000000073121474746205100230450ustar00rootroot00000000000000from typing import Any, Optional import warnings import trio from ..._types import ProxyType from ..._helpers import parse_proxy_url from ..._errors import ProxyConnectionError, ProxyTimeoutError, ProxyError from ._stream import TrioSocketStream from ._resolver import Resolver from ._connect import connect_tcp from ..._protocols.errors import ReplyError from ..._connectors.factory_async import create_connector DEFAULT_TIMEOUT = 60 class TrioProxy: def __init__( self, proxy_type: ProxyType, host: str, port: int, username: Optional[str] = None, password: Optional[str] = None, rdns: Optional[bool] = None, ): self._proxy_type = proxy_type self._proxy_host = host self._proxy_port = port self._password = password self._username = username self._rdns = rdns self._resolver = Resolver() async def connect( self, dest_host: str, dest_port: int, timeout: Optional[float] = None, **kwargs: Any, ) -> trio.socket.SocketType: if timeout is None: timeout = DEFAULT_TIMEOUT _socket = kwargs.get('_socket') if _socket is not None: warnings.warn( "The '_socket' argument is deprecated and will be removed in the future", DeprecationWarning, stacklevel=2, ) local_addr = kwargs.get('local_addr') try: with trio.fail_after(timeout): return await self._connect( dest_host=dest_host, dest_port=dest_port, _socket=_socket, local_addr=local_addr, ) except trio.TooSlowError as e: raise ProxyTimeoutError('Proxy connection timed out: {}'.format(timeout)) from e async def _connect( self, dest_host: str, dest_port: int, _socket=None, local_addr=None, ) -> trio.socket.SocketType: if _socket is None: try: _socket = await connect_tcp( host=self._proxy_host, port=self._proxy_port, local_addr=local_addr, ) except OSError as e: msg = 'Could not connect to proxy {}:{} [{}]'.format( self._proxy_host, self._proxy_port, e.strerror, ) raise ProxyConnectionError(e.errno, msg) from e stream = TrioSocketStream(sock=_socket) try: connector = create_connector( proxy_type=self._proxy_type, username=self._username, password=self._password, rdns=self._rdns, resolver=self._resolver, ) await connector.connect( stream=stream, host=dest_host, port=dest_port, ) return _socket except ReplyError as e: await stream.close() raise ProxyError(e, error_code=e.error_code) except BaseException: # trio.Cancelled... with trio.CancelScope(shield=True): await stream.close() raise @property def proxy_host(self): return self._proxy_host @property def proxy_port(self): return self._proxy_port @classmethod def create(cls, *args, **kwargs): # for backward compatibility return cls(*args, **kwargs) @classmethod def from_url(cls, url: str, **kwargs) -> 'TrioProxy': url_args = parse_proxy_url(url) return cls(*url_args, **kwargs) python-socks-2.7.1/python_socks/async_/trio/_resolver.py000066400000000000000000000011171474746205100235220ustar00rootroot00000000000000import trio from ... import _abc as abc class Resolver(abc.AsyncResolver): async def resolve(self, host, port=0, family=trio.socket.AF_UNSPEC): infos = await trio.socket.getaddrinfo( host=host, port=port, family=family, type=trio.socket.SOCK_STREAM, ) if not infos: # pragma: no cover raise OSError('Can`t resolve address {}:{} [{}]'.format(host, port, family)) infos = sorted(infos, key=lambda info: info[0]) family, _, _, _, address = infos[0] return family, address[0] python-socks-2.7.1/python_socks/async_/trio/_stream.py000066400000000000000000000017531474746205100231620ustar00rootroot00000000000000import trio from ..._errors import ProxyError from ... import _abc as abc DEFAULT_RECEIVE_SIZE = 65536 class TrioSocketStream(abc.AsyncSocketStream): def __init__(self, sock): self._socket = sock async def write_all(self, data): total_sent = 0 while total_sent < len(data): remaining = data[total_sent:] sent = await self._socket.send(remaining) total_sent += sent async def read(self, max_bytes=DEFAULT_RECEIVE_SIZE): return await self._socket.recv(max_bytes) async def read_exact(self, n): data = bytearray() while len(data) < n: packet = await self._socket.recv(n - len(data)) if not packet: # pragma: no cover raise ProxyError('Connection closed unexpectedly') data += packet return data async def close(self): if self._socket is not None: self._socket.close() await trio.lowlevel.checkpoint() python-socks-2.7.1/python_socks/async_/trio/v2/000077500000000000000000000000001474746205100214775ustar00rootroot00000000000000python-socks-2.7.1/python_socks/async_/trio/v2/__init__.py000066400000000000000000000001641474746205100236110ustar00rootroot00000000000000from ._proxy import TrioProxy as Proxy from ._chain import ProxyChain __all__ = ( 'Proxy', 'ProxyChain', ) python-socks-2.7.1/python_socks/async_/trio/v2/_chain.py000066400000000000000000000014331474746205100232730ustar00rootroot00000000000000from typing import Sequence import warnings from ._proxy import TrioProxy class ProxyChain: def __init__(self, proxies: Sequence[TrioProxy]): warnings.warn( 'This implementation of ProxyChain is deprecated and will be removed in the future', DeprecationWarning, stacklevel=2, ) self._proxies = proxies async def connect( self, dest_host, dest_port, dest_ssl=None, timeout=None, ): forward = None for proxy in self._proxies: proxy._forward = forward forward = proxy return await forward.connect( dest_host=dest_host, dest_port=dest_port, dest_ssl=dest_ssl, timeout=timeout, ) python-socks-2.7.1/python_socks/async_/trio/v2/_connect.py000066400000000000000000000005501474746205100236410ustar00rootroot00000000000000from typing import Optional import trio from ._stream import TrioSocketStream async def connect_tcp( host: str, port: int, local_addr: Optional[str] = None, ) -> TrioSocketStream: trio_stream = await trio.open_tcp_stream( host=host, port=port, local_address=local_addr, ) return TrioSocketStream(trio_stream) python-socks-2.7.1/python_socks/async_/trio/v2/_proxy.py000066400000000000000000000077761474746205100234120ustar00rootroot00000000000000import ssl from typing import Any, Optional import trio from ._connect import connect_tcp from ._stream import TrioSocketStream from .._resolver import Resolver from ...._types import ProxyType from ...._helpers import parse_proxy_url from ...._errors import ProxyConnectionError, ProxyTimeoutError, ProxyError from ...._protocols.errors import ReplyError from ...._connectors.factory_async import create_connector DEFAULT_TIMEOUT = 60 class TrioProxy: def __init__( self, proxy_type: ProxyType, host: str, port: int, username: Optional[str] = None, password: Optional[str] = None, rdns: Optional[bool] = None, proxy_ssl: Optional[ssl.SSLContext] = None, forward: Optional['TrioProxy'] = None, ): self._proxy_type = proxy_type self._proxy_host = host self._proxy_port = port self._username = username self._password = password self._rdns = rdns self._proxy_ssl = proxy_ssl self._forward = forward self._resolver = Resolver() async def connect( self, dest_host: str, dest_port: int, dest_ssl: Optional[ssl.SSLContext] = None, timeout: Optional[float] = None, **kwargs: Any, ) -> TrioSocketStream: if timeout is None: timeout = DEFAULT_TIMEOUT local_addr = kwargs.get('local_addr') try: with trio.fail_after(timeout): return await self._connect( dest_host=dest_host, dest_port=dest_port, dest_ssl=dest_ssl, local_addr=local_addr, ) except trio.TooSlowError as e: raise ProxyTimeoutError(f'Proxy connection timed out: {timeout}') from e async def _connect( self, dest_host: str, dest_port: int, dest_ssl: Optional[ssl.SSLContext] = None, local_addr: Optional[str] = None, ) -> TrioSocketStream: if self._forward is None: try: stream = await connect_tcp( host=self._proxy_host, port=self._proxy_port, local_addr=local_addr, ) except OSError as e: raise ProxyConnectionError( e.errno, "Couldn't connect to proxy" f" {self._proxy_host}:{self._proxy_port} [{e.strerror}]", ) from e else: stream = await self._forward.connect( dest_host=self._proxy_host, dest_port=self._proxy_port, ) try: if self._proxy_ssl is not None: stream = await stream.start_tls( hostname=self._proxy_host, ssl_context=self._proxy_ssl, ) connector = create_connector( proxy_type=self._proxy_type, username=self._username, password=self._password, rdns=self._rdns, resolver=self._resolver, ) await connector.connect( stream=stream, host=dest_host, port=dest_port, ) if dest_ssl is not None: stream = await stream.start_tls( hostname=dest_host, ssl_context=dest_ssl, ) except ReplyError as e: await stream.close() raise ProxyError(e, error_code=e.error_code) except BaseException: # trio.Cancelled... with trio.CancelScope(shield=True): await stream.close() raise return stream @classmethod def create(cls, *args, **kwargs): # for backward compatibility return cls(*args, **kwargs) @classmethod def from_url(cls, url: str, **kwargs) -> 'TrioProxy': url_args = parse_proxy_url(url) return cls(*url_args, **kwargs) python-socks-2.7.1/python_socks/async_/trio/v2/_stream.py000066400000000000000000000027031474746205100235050ustar00rootroot00000000000000import ssl from typing import Union import trio from ...._errors import ProxyError from .... import _abc as abc DEFAULT_RECEIVE_SIZE = 65536 TrioStreamType = Union[trio.SocketStream, trio.SSLStream] class TrioSocketStream(abc.AsyncSocketStream): _stream: TrioStreamType def __init__(self, stream: TrioStreamType): self._stream = stream async def write_all(self, data): await self._stream.send_all(data) async def read(self, max_bytes=DEFAULT_RECEIVE_SIZE): return await self._stream.receive_some(max_bytes) async def read_exact(self, n): data = bytearray() while len(data) < n: packet = await self._stream.receive_some(n - len(data)) if not packet: # pragma: no cover raise ProxyError('Connection closed unexpectedly') data += packet return data async def start_tls( self, hostname: str, ssl_context: ssl.SSLContext, ) -> 'TrioSocketStream': ssl_stream = trio.SSLStream( self._stream, ssl_context=ssl_context, server_hostname=hostname, https_compatible=True, server_side=False, ) await ssl_stream.do_handshake() return TrioSocketStream(ssl_stream) async def close(self): await self._stream.aclose() @property def trio_stream(self) -> TrioStreamType: # pragma: nocover return self._stream python-socks-2.7.1/python_socks/py.typed000066400000000000000000000000001474746205100204040ustar00rootroot00000000000000python-socks-2.7.1/python_socks/sync/000077500000000000000000000000001474746205100176735ustar00rootroot00000000000000python-socks-2.7.1/python_socks/sync/__init__.py000066400000000000000000000001521474746205100220020ustar00rootroot00000000000000from ._proxy import SyncProxy as Proxy from ._chain import ProxyChain __all__ = ('Proxy', 'ProxyChain') python-socks-2.7.1/python_socks/sync/_chain.py000066400000000000000000000017151474746205100214720ustar00rootroot00000000000000from typing import Iterable import warnings from ._proxy import SyncProxy class ProxyChain: def __init__(self, proxies: Iterable[SyncProxy]): warnings.warn( 'This implementation of ProxyChain is deprecated and will be removed in the future', DeprecationWarning, stacklevel=2, ) self._proxies = proxies def connect(self, dest_host, dest_port, timeout=None): curr_socket = None proxies = list(self._proxies) length = len(proxies) - 1 for i in range(length): curr_socket = proxies[i].connect( dest_host=proxies[i + 1].proxy_host, dest_port=proxies[i + 1].proxy_port, timeout=timeout, _socket=curr_socket, ) curr_socket = proxies[length].connect( dest_host=dest_host, dest_port=dest_port, timeout=timeout, _socket=curr_socket ) return curr_socket python-socks-2.7.1/python_socks/sync/_connect.py000066400000000000000000000005301474746205100220330ustar00rootroot00000000000000import socket from typing import Optional, Tuple def connect_tcp( host: str, port: int, timeout: Optional[float] = None, local_addr: Optional[Tuple[str, int]] = None, ) -> socket.socket: address = (host, port) return socket.create_connection( address, timeout, source_address=local_addr, ) python-socks-2.7.1/python_socks/sync/_proxy.py000066400000000000000000000063271474746205100215750ustar00rootroot00000000000000import socket from typing import Optional, Any import warnings from .._errors import ProxyConnectionError, ProxyTimeoutError, ProxyError from .._types import ProxyType from .._helpers import parse_proxy_url from .._protocols.errors import ReplyError from .._connectors.factory_sync import create_connector from ._stream import SyncSocketStream from ._resolver import SyncResolver from ._connect import connect_tcp DEFAULT_TIMEOUT = 60 class SyncProxy: def __init__( self, proxy_type: ProxyType, host: str, port: int, username: Optional[str] = None, password: Optional[str] = None, rdns: Optional[bool] = None, ): self._proxy_type = proxy_type self._proxy_host = host self._proxy_port = port self._password = password self._username = username self._rdns = rdns self._resolver = SyncResolver() def connect( self, dest_host: str, dest_port: int, timeout: Optional[float] = None, **kwargs: Any, ) -> socket.socket: if timeout is None: timeout = DEFAULT_TIMEOUT _socket = kwargs.get('_socket') if _socket is not None: warnings.warn( "The '_socket' argument is deprecated and will be removed in the future", DeprecationWarning, stacklevel=2, ) if _socket is None: local_addr = kwargs.get('local_addr') try: _socket = connect_tcp( host=self._proxy_host, port=self._proxy_port, timeout=timeout, local_addr=local_addr, ) except OSError as e: msg = 'Could not connect to proxy {}:{} [{}]'.format( self._proxy_host, self._proxy_port, e.strerror, ) raise ProxyConnectionError(e.errno, msg) from e stream = SyncSocketStream(_socket) try: connector = create_connector( proxy_type=self._proxy_type, username=self._username, password=self._password, rdns=self._rdns, resolver=self._resolver, ) connector.connect( stream=stream, host=dest_host, port=dest_port, ) return _socket except socket.timeout as e: stream.close() raise ProxyTimeoutError('Proxy connection timed out: {}'.format(timeout)) from e except ReplyError as e: stream.close() raise ProxyError(e, error_code=e.error_code) except Exception: stream.close() raise @property def proxy_host(self): return self._proxy_host @property def proxy_port(self): return self._proxy_port @classmethod def create(cls, *args, **kwargs): # for backward compatibility return cls(*args, **kwargs) @classmethod def from_url(cls, url: str, **kwargs) -> 'SyncProxy': url_args = parse_proxy_url(url) return cls(*url_args, **kwargs) python-socks-2.7.1/python_socks/sync/_resolver.py000066400000000000000000000010431474746205100222430ustar00rootroot00000000000000import socket from .. import _abc as abc class SyncResolver(abc.SyncResolver): # noinspection PyMethodMayBeStatic def resolve(self, host, port=0, family=socket.AF_UNSPEC): infos = socket.getaddrinfo(host=host, port=port, family=family, type=socket.SOCK_STREAM) if not infos: # pragma: no cover raise OSError('Can`t resolve address {}:{} [{}]'.format(host, port, family)) infos = sorted(infos, key=lambda info: info[0]) family, _, _, _, address = infos[0] return family, address[0] python-socks-2.7.1/python_socks/sync/_stream.py000066400000000000000000000014621474746205100217020ustar00rootroot00000000000000import socket from .._errors import ProxyError from .. import _abc as abc DEFAULT_RECEIVE_SIZE = 65536 class SyncSocketStream(abc.SyncSocketStream): _socket: socket.socket = None def __init__(self, sock: socket.socket): self._socket = sock def write_all(self, data): self._socket.sendall(data) def read(self, max_bytes=DEFAULT_RECEIVE_SIZE): return self._socket.recv(max_bytes) def read_exact(self, n): data = bytearray() while len(data) < n: packet = self._socket.recv(n - len(data)) if not packet: # pragma: no cover raise ProxyError('Connection closed unexpectedly') data += packet return data def close(self): if self._socket is not None: self._socket.close() python-socks-2.7.1/python_socks/sync/v2/000077500000000000000000000000001474746205100202225ustar00rootroot00000000000000python-socks-2.7.1/python_socks/sync/v2/__init__.py000066400000000000000000000001641474746205100223340ustar00rootroot00000000000000from ._proxy import SyncProxy as Proxy from ._chain import ProxyChain __all__ = ( 'Proxy', 'ProxyChain', ) python-socks-2.7.1/python_socks/sync/v2/_chain.py000066400000000000000000000011031474746205100220100ustar00rootroot00000000000000from typing import Iterable from ._proxy import SyncProxy class ProxyChain: def __init__(self, proxies: Iterable[SyncProxy]): self._proxies = proxies def connect( self, dest_host, dest_port, dest_ssl=None, timeout=None, ): forward = None for proxy in self._proxies: proxy._forward = forward forward = proxy return forward.connect( dest_host=dest_host, dest_port=dest_port, dest_ssl=dest_ssl, timeout=timeout, ) python-socks-2.7.1/python_socks/sync/v2/_connect.py000066400000000000000000000006441474746205100223700ustar00rootroot00000000000000import socket from typing import Optional, Tuple from ._stream import SyncSocketStream def connect_tcp( host: str, port: int, timeout: Optional[float] = None, local_addr: Optional[Tuple[str, int]] = None, ) -> SyncSocketStream: address = (host, port) sock = socket.create_connection( address, timeout, source_address=local_addr, ) return SyncSocketStream(sock) python-socks-2.7.1/python_socks/sync/v2/_proxy.py000066400000000000000000000070451474746205100221220ustar00rootroot00000000000000import socket import ssl from typing import Any, Optional from ._connect import connect_tcp from ._stream import SyncSocketStream from .._resolver import SyncResolver from ..._types import ProxyType from ..._errors import ProxyConnectionError, ProxyTimeoutError, ProxyError from ..._helpers import parse_proxy_url from ..._protocols.errors import ReplyError from ..._connectors.factory_sync import create_connector DEFAULT_TIMEOUT = 60 class SyncProxy: def __init__( self, proxy_type: ProxyType, host: str, port: int, username: Optional[str] = None, password: Optional[str] = None, rdns: Optional[bool] = None, proxy_ssl: Optional[ssl.SSLContext] = None, forward: Optional['SyncProxy'] = None, ): self._proxy_type = proxy_type self._proxy_host = host self._proxy_port = port self._username = username self._password = password self._rdns = rdns self._proxy_ssl = proxy_ssl self._forward = forward self._resolver = SyncResolver() def connect( self, dest_host: str, dest_port: int, dest_ssl: Optional[ssl.SSLContext] = None, timeout: Optional[float] = None, **kwargs: Any, ) -> SyncSocketStream: if timeout is None: timeout = DEFAULT_TIMEOUT if self._forward is None: local_addr = kwargs.get('local_addr') try: stream = connect_tcp( host=self._proxy_host, port=self._proxy_port, timeout=timeout, local_addr=local_addr, ) except OSError as e: msg = 'Could not connect to proxy {}:{} [{}]'.format( self._proxy_host, self._proxy_port, e.strerror, ) raise ProxyConnectionError(e.errno, msg) from e else: stream = self._forward.connect( dest_host=self._proxy_host, dest_port=self._proxy_port, timeout=timeout, ) try: if self._proxy_ssl is not None: stream = stream.start_tls( hostname=self._proxy_host, ssl_context=self._proxy_ssl, ) connector = create_connector( proxy_type=self._proxy_type, username=self._username, password=self._password, rdns=self._rdns, resolver=self._resolver, ) connector.connect( stream=stream, host=dest_host, port=dest_port, ) if dest_ssl is not None: stream = stream.start_tls( hostname=dest_host, ssl_context=dest_ssl, ) return stream except socket.timeout as e: stream.close() raise ProxyTimeoutError(f'Proxy connection timed out: {timeout}') from e except ReplyError as e: stream.close() raise ProxyError(e, error_code=e.error_code) except Exception: stream.close() raise @classmethod def create(cls, *args, **kwargs): # for backward compatibility return cls(*args, **kwargs) @classmethod def from_url(cls, url: str, **kwargs) -> 'SyncProxy': url_args = parse_proxy_url(url) return cls(*url_args, **kwargs) python-socks-2.7.1/python_socks/sync/v2/_ssl_transport.py000066400000000000000000000137101474746205100236520ustar00rootroot00000000000000""" Copied from urllib3.util.ssltransport """ import io import socket import ssl SSL_BLOCKSIZE = 16384 class SSLTransport: """ The SSLTransport wraps an existing socket and establishes an SSL connection. Contrary to Python's implementation of SSLSocket, it allows you to chain multiple TLS connections together. It's particularly useful if you need to implement TLS within TLS. The class supports most of the socket API operations. """ def __init__( self, socket, ssl_context, server_hostname=None, suppress_ragged_eofs=True ): """ Create an SSLTransport around socket using the provided ssl_context. """ self.incoming = ssl.MemoryBIO() self.outgoing = ssl.MemoryBIO() self.suppress_ragged_eofs = suppress_ragged_eofs self.socket = socket self.sslobj = ssl_context.wrap_bio( self.incoming, self.outgoing, server_hostname=server_hostname ) # Perform initial handshake. self._ssl_io_loop(self.sslobj.do_handshake) def __enter__(self): return self def __exit__(self, *_): self.close() def fileno(self): return self.socket.fileno() def read(self, len=1024, buffer=None): return self._wrap_ssl_read(len, buffer) def recv(self, len=1024, flags=0): if flags != 0: raise ValueError("non-zero flags not allowed in calls to recv") return self._wrap_ssl_read(len) def recv_into(self, buffer, nbytes=None, flags=0): if flags != 0: raise ValueError("non-zero flags not allowed in calls to recv_into") if buffer and (nbytes is None): nbytes = len(buffer) elif nbytes is None: nbytes = 1024 return self.read(nbytes, buffer) def sendall(self, data, flags=0): if flags != 0: raise ValueError("non-zero flags not allowed in calls to sendall") count = 0 with memoryview(data) as view, view.cast("B") as byte_view: amount = len(byte_view) while count < amount: v = self.send(byte_view[count:]) count += v def send(self, data, flags=0): if flags != 0: raise ValueError("non-zero flags not allowed in calls to send") response = self._ssl_io_loop(self.sslobj.write, data) return response def makefile( self, mode="r", buffering=None, encoding=None, errors=None, newline=None ): """ Python's httpclient uses makefile and buffered io when reading HTTP messages and we need to support it. This is unfortunately a copy and paste of socket.py makefile with small changes to point to the socket directly. """ if not set(mode) <= {"r", "w", "b"}: raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) writing = "w" in mode reading = "r" in mode or not writing assert reading or writing binary = "b" in mode rawmode = "" if reading: rawmode += "r" if writing: rawmode += "w" raw = socket.SocketIO(self, rawmode) self.socket._io_refs += 1 if buffering is None: buffering = -1 if buffering < 0: buffering = io.DEFAULT_BUFFER_SIZE if buffering == 0: if not binary: raise ValueError("unbuffered streams must be binary") return raw if reading and writing: buffer = io.BufferedRWPair(raw, raw, buffering) elif reading: buffer = io.BufferedReader(raw, buffering) else: assert writing buffer = io.BufferedWriter(raw, buffering) if binary: return buffer text = io.TextIOWrapper(buffer, encoding, errors, newline) text.mode = mode return text def unwrap(self): self._ssl_io_loop(self.sslobj.unwrap) def close(self): self.socket.close() def getpeercert(self, binary_form=False): return self.sslobj.getpeercert(binary_form) def version(self): return self.sslobj.version() def cipher(self): return self.sslobj.cipher() def selected_alpn_protocol(self): return self.sslobj.selected_alpn_protocol() def selected_npn_protocol(self): return self.sslobj.selected_npn_protocol() def shared_ciphers(self): return self.sslobj.shared_ciphers() def compression(self): return self.sslobj.compression() def settimeout(self, value): self.socket.settimeout(value) def gettimeout(self): return self.socket.gettimeout() def _decref_socketios(self): self.socket._decref_socketios() def _wrap_ssl_read(self, len, buffer=None): try: return self._ssl_io_loop(self.sslobj.read, len, buffer) except ssl.SSLError as e: if e.errno == ssl.SSL_ERROR_EOF and self.suppress_ragged_eofs: return 0 # eof, return 0. else: raise def _ssl_io_loop(self, func, *args): """Performs an I/O loop between incoming/outgoing and the socket.""" should_loop = True ret = None while should_loop: errno = None try: ret = func(*args) except ssl.SSLError as e: if e.errno not in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): # WANT_READ, and WANT_WRITE are expected, others are not. raise e errno = e.errno buf = self.outgoing.read() self.socket.sendall(buf) if errno is None: should_loop = False elif errno == ssl.SSL_ERROR_WANT_READ: buf = self.socket.recv(SSL_BLOCKSIZE) if buf: self.incoming.write(buf) else: self.incoming.write_eof() return ret python-socks-2.7.1/python_socks/sync/v2/_stream.py000066400000000000000000000030101474746205100222200ustar00rootroot00000000000000import socket import ssl from typing import Union from ._ssl_transport import SSLTransport from ..._errors import ProxyError from ... import _abc as abc DEFAULT_RECEIVE_SIZE = 65536 SocketType = Union[socket.socket, ssl.SSLSocket, SSLTransport] class SyncSocketStream(abc.SyncSocketStream): _socket: SocketType def __init__(self, sock: SocketType): self._socket = sock def write_all(self, data): self._socket.sendall(data) def read(self, max_bytes=DEFAULT_RECEIVE_SIZE): return self._socket.recv(max_bytes) def read_exact(self, n): data = bytearray() while len(data) < n: packet = self._socket.recv(n - len(data)) if not packet: # pragma: no cover raise ProxyError('Connection closed unexpectedly') data += packet return data def start_tls(self, hostname: str, ssl_context: ssl.SSLContext) -> 'SyncSocketStream': if isinstance(self._socket, (ssl.SSLSocket, SSLTransport)): ssl_socket = SSLTransport( self._socket, ssl_context=ssl_context, server_hostname=hostname, ) else: # plain socket? ssl_socket = ssl_context.wrap_socket( self._socket, server_hostname=hostname, ) return SyncSocketStream(ssl_socket) def close(self): self._socket.close() @property def socket(self) -> SocketType: # pragma: nocover return self._socket python-socks-2.7.1/requirements-dev.txt000066400000000000000000000005001474746205100202270ustar00rootroot00000000000000flake8>=3.9.1 pytest==8.0.2 pytest-cov==4.1.0 pytest-asyncio==0.23.5 trio>=0.24.0 pytest-trio==0.8.0 trustme==0.9.0 attrs>=22.1.0 curio>=1.4; python_version < "3.12" # https://github.com/dabeaz/curio/issues/367 anyio>=3.3.4,<5.0.0 yarl>=1.4.2 async-timeout==4.0.0; python_version < "3.11" flask>=1.1.2 tiny-proxy>=0.1.1 python-socks-2.7.1/tests/000077500000000000000000000000001474746205100153365ustar00rootroot00000000000000python-socks-2.7.1/tests/__init__.py000066400000000000000000000000311474746205100174410ustar00rootroot00000000000000# -*- coding: utf-8 -*- python-socks-2.7.1/tests/config.py000066400000000000000000000042141474746205100171560ustar00rootroot00000000000000import os LOGIN = 'admin' PASSWORD = 'admin' PROXY_HOST_IPV4 = '127.0.0.1' PROXY_HOST_IPV6 = '::1' PROXY_HOST_NAME_IPV4 = 'ip4.proxy.example.com' PROXY_HOST_NAME_IPV6 = 'ip6.proxy.example.com' SOCKS5_PROXY_PORT = 7780 SOCKS5_PROXY_PORT_NO_AUTH = 7781 SOCKS4_PROXY_PORT = 7782 SOCKS4_PORT_NO_AUTH = 7783 HTTP_PROXY_PORT = 7784 HTTPS_PROXY_PORT = 7785 SKIP_IPV6_TESTS = 'SKIP_IPV6_TESTS' in os.environ SOCKS5_IPV4_URL = 'socks5://{login}:{password}@{host}:{port}'.format( host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, login=LOGIN, password=PASSWORD, ) SOCKS5_IPV6_URL = 'socks5://{login}:{password}@{host}:{port}'.format( host='[%s]' % PROXY_HOST_IPV6, port=SOCKS5_PROXY_PORT, login=LOGIN, password=PASSWORD, ) SOCKS5_IPV4_HOSTNAME_URL = 'socks5://{login}:{password}@{host}:{port}'.format( host=PROXY_HOST_NAME_IPV4, port=SOCKS5_PROXY_PORT, login=LOGIN, password=PASSWORD, ) SOCKS5_IPV4_URL_WO_AUTH = 'socks5://{host}:{port}'.format( host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT_NO_AUTH ) SOCKS4_URL = 'socks4://{login}:{password}@{host}:{port}'.format( host=PROXY_HOST_IPV4, port=SOCKS4_PROXY_PORT, login=LOGIN, password='', ) HTTP_PROXY_URL = 'http://{login}:{password}@{host}:{port}'.format( host=PROXY_HOST_IPV4, port=HTTP_PROXY_PORT, login=LOGIN, password=PASSWORD, ) HTTPS_PROXY_URL = 'http://{login}:{password}@{host}:{port}'.format( host=PROXY_HOST_NAME_IPV4, port=HTTPS_PROXY_PORT, login=LOGIN, password=PASSWORD, ) TEST_HOST_IPV4 = '127.0.0.1' TEST_HOST_IPV6 = '::1' TEST_HOST_NAME_IPV4 = 'ip4.target.example.com' TEST_HOST_NAME_IPV6 = 'ip6.target.example.com' TEST_PORT_IPV4 = 8889 TEST_PORT_IPV6 = 8889 TEST_PORT_IPV4_HTTPS = 8890 TEST_URL_IPV4 = 'http://{host}:{port}/ip'.format(host=TEST_HOST_NAME_IPV4, port=TEST_PORT_IPV4) TEST_URL_IPv6 = 'http://{host}:{port}/ip'.format(host=TEST_HOST_NAME_IPV6, port=TEST_PORT_IPV6) TEST_URL_IPV4_HTTPS = 'https://{host}:{port}/ip'.format( host=TEST_HOST_NAME_IPV4, port=TEST_PORT_IPV4_HTTPS ) def resolve_path(path): return os.path.normpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), path)) python-socks-2.7.1/tests/conftest.py000066400000000000000000000156231474746205100175440ustar00rootroot00000000000000import ssl from contextlib import contextmanager from unittest import mock import pytest import trustme from python_socks.async_.asyncio._resolver import Resolver as AsyncioResolver from python_socks.sync._resolver import SyncResolver from tests.config import ( PROXY_HOST_IPV4, PROXY_HOST_IPV6, PROXY_HOST_NAME_IPV4, PROXY_HOST_NAME_IPV6, SOCKS5_PROXY_PORT, LOGIN, PASSWORD, SKIP_IPV6_TESTS, HTTP_PROXY_PORT, SOCKS4_PORT_NO_AUTH, SOCKS4_PROXY_PORT, SOCKS5_PROXY_PORT_NO_AUTH, TEST_PORT_IPV4, TEST_PORT_IPV6, TEST_HOST_IPV4, TEST_HOST_IPV6, TEST_HOST_NAME_IPV4, TEST_HOST_NAME_IPV6, TEST_PORT_IPV4_HTTPS, HTTPS_PROXY_PORT, ) from tests.http_server import HttpServer, HttpServerConfig from tests.mocks import sync_resolve_factory, async_resolve_factory from tests.proxy_server import ProxyConfig, ProxyServer from tests.utils import wait_until_connectable @contextmanager def nullcontext(): yield None @pytest.fixture(scope='session') def target_ssl_ca() -> trustme.CA: return trustme.CA() @pytest.fixture(scope='session') def target_ssl_cert(target_ssl_ca) -> trustme.LeafCert: return target_ssl_ca.issue_cert( 'localhost', TEST_HOST_IPV4, TEST_HOST_IPV6, TEST_HOST_NAME_IPV4, TEST_HOST_NAME_IPV6, ) @pytest.fixture(scope='session') def target_ssl_certfile(target_ssl_cert): with target_ssl_cert.cert_chain_pems[0].tempfile() as cert_path: yield cert_path @pytest.fixture(scope='session') def target_ssl_keyfile(target_ssl_cert): with target_ssl_cert.private_key_pem.tempfile() as private_key_path: yield private_key_path @pytest.fixture(scope='session') def target_ssl_context(target_ssl_ca) -> ssl.SSLContext: ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_ctx.verify_mode = ssl.CERT_REQUIRED ssl_ctx.check_hostname = True target_ssl_ca.configure_trust(ssl_ctx) return ssl_ctx @pytest.fixture(scope='session') def proxy_ssl_ca() -> trustme.CA: return trustme.CA() @pytest.fixture(scope='session') def proxy_ssl_cert(proxy_ssl_ca) -> trustme.LeafCert: return proxy_ssl_ca.issue_cert( 'localhost', PROXY_HOST_IPV4, PROXY_HOST_IPV6, PROXY_HOST_NAME_IPV4, PROXY_HOST_NAME_IPV6, ) @pytest.fixture(scope='session') def proxy_ssl_context(proxy_ssl_ca) -> ssl.SSLContext: ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_ctx.verify_mode = ssl.CERT_REQUIRED ssl_ctx.check_hostname = True proxy_ssl_ca.configure_trust(ssl_ctx) return ssl_ctx @pytest.fixture(scope='session') def proxy_ssl_certfile(proxy_ssl_cert): with proxy_ssl_cert.cert_chain_pems[0].tempfile() as cert_path: yield cert_path @pytest.fixture(scope='session') def proxy_ssl_keyfile(proxy_ssl_cert): with proxy_ssl_cert.private_key_pem.tempfile() as private_key_path: yield private_key_path @pytest.fixture(scope='session', autouse=True) def patch_resolvers(): p1 = mock.patch.object( SyncResolver, attribute='resolve', new=sync_resolve_factory(SyncResolver), ) p2 = mock.patch.object( AsyncioResolver, attribute='resolve', new=async_resolve_factory(AsyncioResolver), ) try: # noinspection PyProtectedMember from python_socks.async_.trio._resolver import Resolver as TrioResolver except ImportError: p3 = nullcontext() else: p3 = mock.patch.object( TrioResolver, attribute='resolve', new=async_resolve_factory(TrioResolver), ) try: # noinspection PyProtectedMember from python_socks.async_.curio._resolver import Resolver as CurioResolver except ImportError: p4 = nullcontext() else: p4 = mock.patch.object( CurioResolver, attribute='resolve', new=async_resolve_factory(CurioResolver), ) try: from python_socks.async_.anyio._resolver import Resolver as AnyioResolver except ImportError: p5 = nullcontext() else: p5 = mock.patch.object( AnyioResolver, attribute='resolve', new=async_resolve_factory(AnyioResolver), ) with p1, p2, p3, p4, p5: yield None @pytest.fixture(scope='session', autouse=True) def proxy_server(proxy_ssl_certfile, proxy_ssl_keyfile): config = [ ProxyConfig( proxy_type='http', host=PROXY_HOST_IPV4, port=HTTP_PROXY_PORT, username=LOGIN, password=PASSWORD, ), ProxyConfig( proxy_type='socks4', host=PROXY_HOST_IPV4, port=SOCKS4_PROXY_PORT, username=LOGIN, password=None, ), ProxyConfig( proxy_type='socks4', host=PROXY_HOST_IPV4, port=SOCKS4_PORT_NO_AUTH, username=None, password=None, ), ProxyConfig( proxy_type='socks5', host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD, ), ProxyConfig( proxy_type='socks5', host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT_NO_AUTH, username=None, password=None, ), ProxyConfig( proxy_type='http', # host=PROXY_HOST_NAME_IPV4, host=PROXY_HOST_IPV4, port=HTTPS_PROXY_PORT, username=LOGIN, password=PASSWORD, ssl_certfile=proxy_ssl_certfile, ssl_keyfile=proxy_ssl_keyfile, ), ] if not SKIP_IPV6_TESTS: config.append( ProxyConfig( proxy_type='socks5', host=PROXY_HOST_IPV6, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD, ), ) server = ProxyServer(config=config) server.start() for cfg in config: wait_until_connectable(host=cfg.host, port=cfg.port, timeout=10) yield None server.terminate() @pytest.fixture(scope='session', autouse=True) def web_server(target_ssl_certfile, target_ssl_keyfile): config = [ HttpServerConfig( host=TEST_HOST_IPV4, port=TEST_PORT_IPV4, ), HttpServerConfig( host=TEST_HOST_IPV4, port=TEST_PORT_IPV4_HTTPS, # certfile=TEST_HOST_CERT_FILE, # keyfile=TEST_HOST_KEY_FILE, certfile=target_ssl_certfile, keyfile=target_ssl_keyfile, ), ] if not SKIP_IPV6_TESTS: config.append(HttpServerConfig(host=TEST_HOST_IPV6, port=TEST_PORT_IPV6)) server = HttpServer(config=config) server.start() for cfg in config: server.wait_until_connectable(host=cfg.host, port=cfg.port) yield None server.terminate() python-socks-2.7.1/tests/http_app.py000066400000000000000000000011011474746205100175200ustar00rootroot00000000000000import ssl import flask # noqa from flask import request # noqa app = flask.Flask(__name__) @app.route('/ip') def ip(): return request.remote_addr def run_app(host: str, port: int, certfile: str = None, keyfile: str = None): if certfile and keyfile: ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS) ssl_context.load_cert_chain(certfile, keyfile) else: ssl_context = None print('Starting http server on {}:{}...'.format(host, port)) app.run(debug=False, host=host, port=port, threaded=True, ssl_context=ssl_context) python-socks-2.7.1/tests/http_server.py000066400000000000000000000024351474746205100202610ustar00rootroot00000000000000import typing import time from multiprocessing import Process from tests.utils import is_connectable from tests.http_app import run_app class HttpServerConfig(typing.NamedTuple): host: str port: int certfile: str = None keyfile: str = None def to_dict(self): d = {} for key, val in self._asdict().items(): if val is not None: d[key] = val return d class HttpServer: def __init__(self, config: typing.Iterable[HttpServerConfig]): self.config = config self.workers = [] def start(self): for cfg in self.config: p = Process(target=run_app, kwargs=cfg.to_dict()) self.workers.append(p) for p in self.workers: p.start() def terminate(self): for p in self.workers: p.terminate() def wait_until_connectable(self, host, port, timeout=10): count = 0 while not is_connectable(host=host, port=port): if count >= timeout: self.terminate() raise Exception( 'The http server has not available ' 'by (%s, %s) in %d seconds' % (host, port, timeout)) count += 1 time.sleep(1) return True python-socks-2.7.1/tests/mocks.py000066400000000000000000000042711474746205100170300ustar00rootroot00000000000000import socket from tests.config import ( TEST_HOST_NAME_IPV4, PROXY_HOST_NAME_IPV4, TEST_HOST_NAME_IPV6, PROXY_HOST_NAME_IPV6, ) def getaddrinfo_sync_mock(): _orig_getaddrinfo = socket.getaddrinfo def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0): if host in (TEST_HOST_NAME_IPV4, PROXY_HOST_NAME_IPV4): return [(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('127.0.0.1', port))] if host in (TEST_HOST_NAME_IPV6, PROXY_HOST_NAME_IPV6): return [(socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('::1', port, 0, 0))] return _orig_getaddrinfo(host, port, family, type, proto, flags) return getaddrinfo def getaddrinfo_async_mock(origin_getaddrinfo): async def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0): if host in (TEST_HOST_NAME_IPV4, PROXY_HOST_NAME_IPV4): return [(socket.AF_INET, socket.SOCK_STREAM, 6, '', ('127.0.0.1', port))] if host in (TEST_HOST_NAME_IPV6, PROXY_HOST_NAME_IPV6): return [(socket.AF_INET6, socket.SOCK_STREAM, 6, '', ('::1', port, 0, 0))] return await origin_getaddrinfo( host, port, family=family, type=type, proto=proto, flags=flags, ) return getaddrinfo def _resolve_local(host): if host in (TEST_HOST_NAME_IPV4, PROXY_HOST_NAME_IPV4): return socket.AF_INET, '127.0.0.1' if host in (TEST_HOST_NAME_IPV6, PROXY_HOST_NAME_IPV6): return socket.AF_INET6, '::1' return None def sync_resolve_factory(cls): original_resolve = cls.resolve def new_resolve(self, host, port=0, family=socket.AF_UNSPEC): res = _resolve_local(host) if res is not None: return res return original_resolve(self, host=host, port=port, family=family) return new_resolve def async_resolve_factory(cls): original_resolve = cls.resolve async def new_resolve(self, host, port=0, family=socket.AF_UNSPEC): res = _resolve_local(host) if res is not None: return res return await original_resolve(self, host=host, port=port, family=family) return new_resolve python-socks-2.7.1/tests/proxy_server.py000066400000000000000000000064501474746205100204640ustar00rootroot00000000000000import ssl import typing from multiprocessing import Process from unittest import mock import anyio from anyio import create_tcp_listener from anyio.streams.tls import TLSListener from tiny_proxy import ( HttpProxyHandler, Socks5ProxyHandler, Socks4ProxyHandler, HttpProxy, Socks4Proxy, Socks5Proxy, AbstractProxy, ) from tests.mocks import getaddrinfo_async_mock class ProxyConfig(typing.NamedTuple): proxy_type: str host: str port: int username: typing.Optional[str] = None password: typing.Optional[str] = None ssl_certfile: typing.Optional[str] = None ssl_keyfile: typing.Optional[str] = None def to_dict(self): d = {} for key, val in self._asdict().items(): if val is not None: d[key] = val return d cls_map = { 'http': HttpProxyHandler, 'socks4': Socks4ProxyHandler, 'socks5': Socks5ProxyHandler, } def connect_to_remote_factory(cls: typing.Type[AbstractProxy]): """ simulate target host connection timeout """ origin_connect_to_remote = cls.connect_to_remote async def new_connect_to_remote(self): await anyio.sleep(0.01) return await origin_connect_to_remote(self) return new_connect_to_remote @mock.patch.object( HttpProxy, attribute='connect_to_remote', new=connect_to_remote_factory(HttpProxy), ) @mock.patch.object( Socks4Proxy, attribute='connect_to_remote', new=connect_to_remote_factory(Socks4Proxy), ) @mock.patch.object( Socks5Proxy, attribute='connect_to_remote', new=connect_to_remote_factory(Socks5Proxy), ) @mock.patch('anyio._core._sockets.getaddrinfo', new=getaddrinfo_async_mock(anyio.getaddrinfo)) def start( proxy_type, host, port, ssl_certfile=None, ssl_keyfile=None, **kwargs, ): handler_cls = cls_map.get(proxy_type) if not handler_cls: raise RuntimeError(f'Unsupported type: {proxy_type}') if ssl_certfile and ssl_keyfile: ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ssl_context.load_cert_chain(ssl_certfile, ssl_keyfile) else: ssl_context = None print(f'Starting {proxy_type} proxy on {host}:{port}...') handler = handler_cls(**kwargs) async def serve(): listener = await create_tcp_listener(local_host=host, local_port=port) if ssl_context is not None: listener = TLSListener(listener=listener, ssl_context=ssl_context) async with listener: await listener.serve(handler.handle) anyio.run(serve) class ProxyServer: workers: typing.List[Process] def __init__(self, config: typing.Iterable[ProxyConfig]): self.config = config self.workers = [] def start(self): for cfg in self.config: print( 'Starting {} proxy on {}:{}; certfile={}, keyfile={}...'.format( cfg.proxy_type, cfg.host, cfg.port, cfg.ssl_certfile, cfg.ssl_keyfile, ) ) p = Process(target=start, kwargs=cfg.to_dict(), daemon=True) self.workers.append(p) for p in self.workers: p.start() def terminate(self): for p in self.workers: p.terminate() python-socks-2.7.1/tests/test_misc.py000066400000000000000000000010561474746205100177040ustar00rootroot00000000000000# noinspection PyPackageRequirements import pytest from python_socks._helpers import is_ip_address # noqa from python_socks._protocols.http import BasicAuth # noqa @pytest.mark.parametrize('address', ('::1', b'::1', '127.0.0.1', b'127.0.0.1')) def test_is_ip_address(address): assert is_ip_address(address) def test_basic_auth(): login = 'login' password = 'password' auth1 = BasicAuth(login=login, password=password) auth2 = BasicAuth.decode(auth1.encode()) assert auth2.login == login assert auth2.password == password python-socks-2.7.1/tests/test_proxy_async_aio.py000066400000000000000000000135441474746205100221640ustar00rootroot00000000000000import asyncio import socket import pytest # noqa from yarl import URL # noqa from python_socks import ProxyType, ProxyError, ProxyTimeoutError, ProxyConnectionError from python_socks.async_ import ProxyChain from python_socks.async_.asyncio import Proxy from python_socks.async_.asyncio._resolver import Resolver from tests.config import ( PROXY_HOST_IPV4, SOCKS5_PROXY_PORT, LOGIN, PASSWORD, SKIP_IPV6_TESTS, SOCKS5_IPV4_URL, SOCKS5_IPV4_URL_WO_AUTH, SOCKS5_IPV6_URL, SOCKS4_URL, HTTP_PROXY_URL, TEST_URL_IPV4, SOCKS5_IPV4_HOSTNAME_URL, TEST_URL_IPV4_HTTPS, ) async def make_request( proxy: Proxy, url: str, resolve_host=False, timeout=None, ssl_context=None, ): loop = asyncio.get_event_loop() url = URL(url) dest_host = url.host if resolve_host: resolver = Resolver(loop=loop) _, dest_host = await resolver.resolve(url.host) sock: socket.socket = await proxy.connect( dest_host=dest_host, dest_port=url.port, timeout=timeout ) if url.scheme == 'https': dest_ssl = ssl_context else: dest_ssl = None # noinspection PyTypeChecker reader, writer = await asyncio.open_connection( host=None, port=None, sock=sock, ssl=dest_ssl, server_hostname=url.host if dest_ssl else None, ) request = 'GET {rel_url} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n' request = request.format(rel_url=url.path_qs, host=url.host) request = request.encode('ascii') writer.write(request) status_line = await reader.readline() version, status_code, *reason = status_line.split() writer.transport.close() return int(status_code) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) @pytest.mark.asyncio async def test_socks5_proxy_ipv4(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.asyncio async def test_socks5_proxy_hostname_ipv4(url, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_HOSTNAME_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.asyncio async def test_socks5_proxy_ipv4_with_auth_none(url, rdns, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL_WO_AUTH, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.asyncio async def test_socks5_proxy_with_invalid_credentials(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD + 'aaa', ) with pytest.raises(ProxyError): await make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.asyncio async def test_socks5_proxy_with_connect_timeout(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyTimeoutError): await make_request(proxy=proxy, url=TEST_URL_IPV4, timeout=0.0001) @pytest.mark.asyncio async def test_socks5_proxy_with_invalid_proxy_port(unused_tcp_port): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=unused_tcp_port, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyConnectionError): await make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.skipif(SKIP_IPV6_TESTS, reason="TravisCI doesn't support ipv6") @pytest.mark.asyncio async def test_socks5_proxy_ipv6(url, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV6_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) @pytest.mark.asyncio async def test_socks4_proxy(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.asyncio async def test_http_proxy(url, target_ssl_context): proxy = Proxy.from_url(HTTP_PROXY_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.asyncio async def test_proxy_chain(url, target_ssl_context): proxy = ProxyChain( [ Proxy.from_url(SOCKS5_IPV4_URL), Proxy.from_url(SOCKS4_URL), Proxy.from_url(HTTP_PROXY_URL), ] ) # noinspection PyTypeChecker status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 python-socks-2.7.1/tests/test_proxy_async_aio_v2.py000066400000000000000000000150521474746205100225670ustar00rootroot00000000000000import asyncio import sys from unittest.mock import patch import pytest from yarl import URL from python_socks import ProxyType, ProxyError, ProxyTimeoutError, ProxyConnectionError from python_socks.async_.asyncio._resolver import Resolver from python_socks.async_.asyncio.v2 import Proxy from python_socks.async_.asyncio.v2 import ProxyChain from python_socks.async_.asyncio.v2._proxy import AsyncioProxy from tests.config import ( PROXY_HOST_IPV4, SOCKS5_PROXY_PORT, LOGIN, PASSWORD, SKIP_IPV6_TESTS, SOCKS5_IPV4_URL, SOCKS5_IPV4_URL_WO_AUTH, SOCKS5_IPV6_URL, SOCKS4_URL, HTTP_PROXY_URL, TEST_URL_IPV4, SOCKS5_IPV4_HOSTNAME_URL, TEST_URL_IPV4_HTTPS, TEST_URL_IPv6, ) from tests.mocks import getaddrinfo_async_mock async def make_request( proxy: AsyncioProxy, url: str, resolve_host=False, timeout=None, ssl_context=None, ): loop = asyncio.get_event_loop() with patch.object( loop, attribute='getaddrinfo', new=getaddrinfo_async_mock(loop.getaddrinfo), ): url = URL(url) dest_host = url.host if resolve_host: resolver = Resolver(loop=loop) _, dest_host = await resolver.resolve(url.host) if url.scheme == 'https': dest_ssl = ssl_context else: dest_ssl = None stream = await proxy.connect( dest_host=dest_host, dest_port=url.port, dest_ssl=dest_ssl, timeout=timeout, ) # fmt: off request = ( 'GET {rel_url} HTTP/1.1\r\n' 'Host: {host}\r\n' 'Connection: close\r\n\r\n' ) # fmt: on request = request.format(rel_url=url.path_qs, host=url.host) request = request.encode('ascii') await stream.write_all(request) response = await stream.read(1024) status_line = response.split(b'\r\n', 1)[0] version, status_code, *reason = status_line.split() await stream.close() return int(status_code) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) @pytest.mark.asyncio async def test_socks5_proxy_ipv4(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.skipif(sys.version_info < (3, 7), reason="Buggy asyncio...") @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.asyncio async def test_socks5_proxy_hostname_ipv4(url, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_HOSTNAME_URL) status_code = await make_request(proxy=proxy, url=url, ssl_context=target_ssl_context) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.asyncio async def test_socks5_proxy_ipv4_with_auth_none(url, rdns, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL_WO_AUTH, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.asyncio async def test_socks5_proxy_with_invalid_credentials(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD + 'aaa', ) with pytest.raises(ProxyError): await make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.asyncio async def test_socks5_proxy_with_connect_timeout(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyTimeoutError): await make_request(proxy=proxy, url=TEST_URL_IPV4, timeout=0.0001) @pytest.mark.asyncio async def test_socks5_proxy_with_invalid_proxy_port(unused_tcp_port): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=unused_tcp_port, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyConnectionError): await make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.skipif(SKIP_IPV6_TESTS, reason="TravisCI doesn't support ipv6") @pytest.mark.asyncio async def test_socks5_proxy_ipv6(url, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV6_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.skipif(SKIP_IPV6_TESTS, reason="TravisCI doesn't support ipv6") @pytest.mark.parametrize('rdns', (True, False)) @pytest.mark.asyncio async def test_socks5_proxy_hostname_ipv6(rdns): proxy = Proxy.from_url(SOCKS5_IPV4_URL, rdns=rdns) status_code = await make_request(proxy=proxy, url=TEST_URL_IPv6) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) @pytest.mark.asyncio async def test_socks4_proxy(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.asyncio async def test_http_proxy(url, target_ssl_context): proxy = Proxy.from_url(HTTP_PROXY_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.asyncio async def test_proxy_chain(url, target_ssl_context): proxy = ProxyChain( [ Proxy.from_url(SOCKS5_IPV4_URL), Proxy.from_url(SOCKS4_URL), Proxy.from_url(HTTP_PROXY_URL), ] ) status_code = await make_request( proxy=proxy, # type: ignore url=url, ssl_context=target_ssl_context, ) assert status_code == 200 python-socks-2.7.1/tests/test_proxy_async_anyio.py000066400000000000000000000150221474746205100225240ustar00rootroot00000000000000from unittest.mock import patch import pytest from yarl import URL from python_socks import ProxyType, ProxyError, ProxyTimeoutError, ProxyConnectionError from tests.config import ( PROXY_HOST_IPV4, SOCKS5_PROXY_PORT, LOGIN, PASSWORD, SKIP_IPV6_TESTS, SOCKS5_IPV4_URL, SOCKS5_IPV4_URL_WO_AUTH, SOCKS5_IPV6_URL, SOCKS4_URL, HTTP_PROXY_URL, TEST_URL_IPV4, SOCKS5_IPV4_HOSTNAME_URL, TEST_URL_IPV4_HTTPS, HTTPS_PROXY_URL, ) from tests.mocks import getaddrinfo_async_mock anyio = pytest.importorskip('anyio') from python_socks.async_.anyio._resolver import Resolver # noqa: E402 from python_socks.async_.anyio import Proxy # noqa: E402 from python_socks.async_.anyio import ProxyChain # noqa: E402 from python_socks.async_.anyio._proxy import AnyioProxy # noqa: E402 async def make_request( proxy: AnyioProxy, url: str, resolve_host=False, timeout=None, ssl_context=None, ): # import anyio with patch( 'anyio._core._sockets.getaddrinfo', new=getaddrinfo_async_mock(anyio.getaddrinfo), ): url = URL(url) dest_host = url.host if resolve_host: resolver = Resolver() _, dest_host = await resolver.resolve(url.host) dest_ssl = None if url.scheme == 'https': dest_ssl = ssl_context stream = await proxy.connect( dest_host=dest_host, dest_port=url.port, dest_ssl=dest_ssl, timeout=timeout, ) # fmt: off request = ( 'GET {rel_url} HTTP/1.1\r\n' 'Host: {host}\r\n' 'Connection: close\r\n\r\n' ) # fmt: on request = request.format(rel_url=url.path_qs, host=url.host) request = request.encode('ascii') await stream.write_all(request) response = await stream.read(1024) status_line = response.split(b'\r\n', 1)[0] version, status_code, *reason = status_line.split() await stream.close() return int(status_code) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) @pytest.mark.anyio async def test_socks5_proxy_ipv4(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.anyio async def test_socks5_proxy_hostname_ipv4(url, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_HOSTNAME_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.anyio async def test_socks5_proxy_ipv4_with_auth_none(url, rdns, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL_WO_AUTH, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.anyio async def test_socks5_proxy_with_invalid_credentials(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD + 'aaa', ) with pytest.raises(ProxyError): await make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.anyio async def test_socks5_proxy_with_connect_timeout(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyTimeoutError): await make_request(proxy=proxy, url=TEST_URL_IPV4, timeout=0.0001) @pytest.mark.anyio async def test_socks5_proxy_with_invalid_proxy_port(unused_tcp_port): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=unused_tcp_port, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyConnectionError): await make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.skipif(SKIP_IPV6_TESTS, reason="TravisCI doesn't support ipv6") @pytest.mark.anyio async def test_socks5_proxy_ipv6(url, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV6_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) @pytest.mark.anyio async def test_socks4_proxy(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.anyio async def test_http_proxy(url, target_ssl_context): proxy = Proxy.from_url(HTTP_PROXY_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.anyio async def test_secure_proxy(url, target_ssl_context, proxy_ssl_context): proxy = Proxy.from_url(HTTPS_PROXY_URL, proxy_ssl=proxy_ssl_context) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.anyio async def test_proxy_chain(url, target_ssl_context): proxy = ProxyChain( [ Proxy.from_url(SOCKS5_IPV4_URL), Proxy.from_url(SOCKS4_URL), Proxy.from_url(HTTP_PROXY_URL), ] ) status_code = await make_request( proxy=proxy, # type: ignore url=url, ssl_context=target_ssl_context, ) assert status_code == 200 python-socks-2.7.1/tests/test_proxy_async_anyio_v2.py000066400000000000000000000150331474746205100231350ustar00rootroot00000000000000from unittest.mock import patch import pytest from yarl import URL from python_socks import ProxyType, ProxyError, ProxyTimeoutError, ProxyConnectionError from tests.config import ( PROXY_HOST_IPV4, SOCKS5_PROXY_PORT, LOGIN, PASSWORD, SKIP_IPV6_TESTS, SOCKS5_IPV4_URL, SOCKS5_IPV4_URL_WO_AUTH, SOCKS5_IPV6_URL, SOCKS4_URL, HTTP_PROXY_URL, TEST_URL_IPV4, SOCKS5_IPV4_HOSTNAME_URL, TEST_URL_IPV4_HTTPS, HTTPS_PROXY_URL, ) from tests.mocks import getaddrinfo_async_mock anyio = pytest.importorskip('anyio') from python_socks.async_.anyio._resolver import Resolver # noqa: E402 from python_socks.async_.anyio.v2 import Proxy # noqa: E402 from python_socks.async_.anyio.v2 import ProxyChain # noqa: E402 from python_socks.async_.anyio.v2._proxy import AnyioProxy # noqa: E402 async def make_request( proxy: AnyioProxy, url: str, resolve_host=False, timeout=None, ssl_context=None, ): # import anyio with patch( 'anyio._core._sockets.getaddrinfo', new=getaddrinfo_async_mock(anyio.getaddrinfo), ): url = URL(url) dest_host = url.host if resolve_host: resolver = Resolver() _, dest_host = await resolver.resolve(url.host) dest_ssl = None if url.scheme == 'https': dest_ssl = ssl_context stream = await proxy.connect( dest_host=dest_host, dest_port=url.port, dest_ssl=dest_ssl, timeout=timeout, ) # fmt: off request = ( 'GET {rel_url} HTTP/1.1\r\n' 'Host: {host}\r\n' 'Connection: close\r\n\r\n' ) # fmt: on request = request.format(rel_url=url.path_qs, host=url.host) request = request.encode('ascii') await stream.write_all(request) response = await stream.read(1024) status_line = response.split(b'\r\n', 1)[0] version, status_code, *reason = status_line.split() await stream.close() return int(status_code) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) @pytest.mark.anyio async def test_socks5_proxy_ipv4(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.anyio async def test_socks5_proxy_hostname_ipv4(url, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_HOSTNAME_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.anyio async def test_socks5_proxy_ipv4_with_auth_none(url, rdns, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL_WO_AUTH, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.anyio async def test_socks5_proxy_with_invalid_credentials(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD + 'aaa', ) with pytest.raises(ProxyError): await make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.anyio async def test_socks5_proxy_with_connect_timeout(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyTimeoutError): await make_request(proxy=proxy, url=TEST_URL_IPV4, timeout=0.0001) @pytest.mark.anyio async def test_socks5_proxy_with_invalid_proxy_port(unused_tcp_port): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=unused_tcp_port, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyConnectionError): await make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.skipif(SKIP_IPV6_TESTS, reason="TravisCI doesn't support ipv6") @pytest.mark.anyio async def test_socks5_proxy_ipv6(url, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV6_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) @pytest.mark.anyio async def test_socks4_proxy(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.anyio async def test_http_proxy(url, target_ssl_context): proxy = Proxy.from_url(HTTP_PROXY_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.anyio async def test_secure_proxy(url, target_ssl_context, proxy_ssl_context): proxy = Proxy.from_url(HTTPS_PROXY_URL, proxy_ssl=proxy_ssl_context) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.anyio async def test_proxy_chain(url, target_ssl_context): proxy = ProxyChain( [ Proxy.from_url(SOCKS5_IPV4_URL), Proxy.from_url(SOCKS4_URL), Proxy.from_url(HTTP_PROXY_URL), ] ) status_code = await make_request( proxy=proxy, # type: ignore url=url, ssl_context=target_ssl_context, ) assert status_code == 200 python-socks-2.7.1/tests/test_proxy_async_curio.py000066400000000000000000000160531474746205100225330ustar00rootroot00000000000000from typing import Optional from unittest.mock import patch import pytest from yarl import URL from python_socks import ProxyType, ProxyError, ProxyTimeoutError, ProxyConnectionError from python_socks.async_ import ProxyChain from tests.config import ( PROXY_HOST_IPV4, SOCKS5_PROXY_PORT, LOGIN, PASSWORD, SKIP_IPV6_TESTS, SOCKS5_IPV4_URL, SOCKS5_IPV4_URL_WO_AUTH, SOCKS5_IPV6_URL, SOCKS4_URL, HTTP_PROXY_URL, TEST_URL_IPV4, SOCKS5_IPV4_HOSTNAME_URL, TEST_URL_IPV4_HTTPS, ) from tests.mocks import getaddrinfo_async_mock curio = pytest.importorskip('curio') import curio.io # noqa: E402 import curio.ssl as curiossl # noqa: E402 import curio.socket # noqa: E402 from python_socks.async_.curio._resolver import Resolver # noqa: E402 from python_socks.async_.curio import Proxy # noqa: E402 from python_socks.async_.curio._proxy import CurioProxy # noqa: E402 async def make_request( proxy: CurioProxy, url: str, resolve_host=False, timeout=None, ssl_context=None, ): with patch( 'curio.socket.getaddrinfo', new=getaddrinfo_async_mock(curio.socket.getaddrinfo), ): url = URL(url) dest_host = url.host if resolve_host: resolver = Resolver() _, dest_host = await resolver.resolve(url.host) sock: curio.io.Socket = await proxy.connect( dest_host=dest_host, dest_port=url.port, timeout=timeout ) dest_ssl: Optional[curiossl.CurioSSLContext] = None if url.scheme == 'https': dest_ssl = curiossl.CurioSSLContext(ssl_context) if dest_ssl is not None: sock = await dest_ssl.wrap_socket( sock, do_handshake_on_connect=False, server_hostname=url.host, ) await sock.do_handshake() stream = sock.as_stream() # fmt: off request = ( 'GET {rel_url} HTTP/1.1\r\n' 'Host: {host}\r\n' 'Connection: close\r\n\r\n' ) # fmt: on request = request.format(rel_url=url.path_qs, host=url.host) request = request.encode('ascii') await stream.write(request) response = await stream.read(1024) status_line = response.split(b'\r\n', 1)[0] status_line = status_line.decode('utf-8', 'surrogateescape') version, status_code, *reason = status_line.split() return int(status_code) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) def test_socks5_proxy_ipv4(url, rdns, resolve_host, target_ssl_context): async def main(): proxy = Proxy.from_url(SOCKS5_IPV4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 curio.run(main) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) def test_socks5_proxy_hostname_ipv4(url, target_ssl_context): async def main(): proxy = Proxy.from_url(SOCKS5_IPV4_HOSTNAME_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 curio.run(main) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) def test_socks5_proxy_ipv4_with_auth_none(url, rdns, target_ssl_context): async def main(): proxy = Proxy.from_url(SOCKS5_IPV4_URL_WO_AUTH, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 curio.run(main) def test_socks5_proxy_with_invalid_credentials(): async def main(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD + 'aaa', ) with pytest.raises(ProxyError): await make_request(proxy=proxy, url=TEST_URL_IPV4) curio.run(main) def test_socks5_proxy_with_connect_timeout(): async def main(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyTimeoutError): await make_request(proxy=proxy, url=TEST_URL_IPV4, timeout=0.0001) curio.run(main) def test_socks5_proxy_with_invalid_proxy_port(unused_tcp_port): async def main(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=unused_tcp_port, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyConnectionError): await make_request(proxy=proxy, url=TEST_URL_IPV4) curio.run(main) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.skipif(SKIP_IPV6_TESTS, reason="TravisCI doesn't support ipv6") def test_socks5_proxy_ipv6(url, target_ssl_context): async def main(): proxy = Proxy.from_url(SOCKS5_IPV6_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 curio.run(main) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) def test_socks4_proxy(url, rdns, resolve_host, target_ssl_context): async def main(): proxy = Proxy.from_url(SOCKS4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 curio.run(main) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) def test_http_proxy(url, target_ssl_context): async def main(): proxy = Proxy.from_url(HTTP_PROXY_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 curio.run(main) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) def test_proxy_chain(url, target_ssl_context): async def main(): proxy = ProxyChain( [ Proxy.from_url(SOCKS5_IPV4_URL), Proxy.from_url(SOCKS4_URL), Proxy.from_url(HTTP_PROXY_URL), ] ) status_code = await make_request( proxy=proxy, # type: ignore url=url, ssl_context=target_ssl_context, ) assert status_code == 200 curio.run(main) python-socks-2.7.1/tests/test_proxy_async_trio.py000066400000000000000000000136301474746205100223650ustar00rootroot00000000000000import socket import pytest from yarl import URL from python_socks import ProxyType, ProxyError, ProxyTimeoutError, ProxyConnectionError from python_socks.async_ import ProxyChain from tests.config import ( PROXY_HOST_IPV4, SOCKS5_PROXY_PORT, LOGIN, PASSWORD, SKIP_IPV6_TESTS, SOCKS5_IPV4_URL, SOCKS5_IPV4_URL_WO_AUTH, SOCKS5_IPV6_URL, SOCKS4_URL, HTTP_PROXY_URL, TEST_URL_IPV4, SOCKS5_IPV4_HOSTNAME_URL, TEST_URL_IPV4_HTTPS, ) trio = pytest.importorskip('trio') from python_socks.async_.trio import Proxy # noqa: E402 from python_socks.async_.trio._resolver import Resolver # noqa: E402 async def make_request( proxy: Proxy, url: str, resolve_host=False, timeout=None, ssl_context=None, ): url = URL(url) dest_host = url.host if resolve_host: resolver = Resolver() _, dest_host = await resolver.resolve(url.host) sock: socket.socket = await proxy.connect( dest_host=dest_host, dest_port=url.port, timeout=timeout ) if url.scheme == 'https': dest_ssl = ssl_context else: dest_ssl = None stream = trio.SocketStream(sock) if dest_ssl is not None: stream = trio.SSLStream(stream, dest_ssl, server_hostname=url.host) await stream.do_handshake() # fmt: off request = ( 'GET {rel_url} HTTP/1.1\r\n' 'Host: {host}\r\n' 'Connection: close\r\n\r\n' ) # fmt: on request = request.format(rel_url=url.path_qs, host=url.host) request = request.encode('ascii') await stream.send_all(request) response = await stream.receive_some(1024) status_line = response.split(b'\r\n', 1)[0] status_line = status_line.decode('utf-8', 'surrogateescape') version, status_code, *reason = status_line.split() return int(status_code) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) @pytest.mark.trio async def test_socks5_proxy_ipv4(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.trio async def test_socks5_proxy_hostname_ipv4(url, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_HOSTNAME_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.trio async def test_socks5_proxy_ipv4_with_auth_none(url, rdns, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL_WO_AUTH, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.trio async def test_socks5_proxy_with_invalid_credentials(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD + 'aaa', ) with pytest.raises(ProxyError): await make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.trio async def test_socks5_proxy_with_connect_timeout(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyTimeoutError): await make_request(proxy=proxy, url=TEST_URL_IPV4, timeout=0.0001) @pytest.mark.trio async def test_socks5_proxy_with_invalid_proxy_port(unused_tcp_port): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=unused_tcp_port, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyConnectionError): await make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.skipif(SKIP_IPV6_TESTS, reason="TravisCI doesn't support ipv6") @pytest.mark.trio async def test_socks5_proxy_ipv6(url, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV6_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) @pytest.mark.trio async def test_socks4_proxy(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.trio async def test_http_proxy(url, target_ssl_context): proxy = Proxy.from_url(HTTP_PROXY_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.trio async def test_proxy_chain(url, target_ssl_context): proxy = ProxyChain( [ Proxy.from_url(SOCKS5_IPV4_URL), Proxy.from_url(SOCKS4_URL), Proxy.from_url(HTTP_PROXY_URL), ] ) status_code = await make_request( proxy=proxy, # type: ignore url=url, ssl_context=target_ssl_context, ) assert status_code == 200 python-socks-2.7.1/tests/test_proxy_async_trio_v2.py000066400000000000000000000150301474746205100227700ustar00rootroot00000000000000from unittest.mock import patch import pytest from yarl import URL from python_socks import ProxyType, ProxyError, ProxyTimeoutError, ProxyConnectionError from tests.config import ( PROXY_HOST_IPV4, SOCKS5_PROXY_PORT, LOGIN, PASSWORD, SKIP_IPV6_TESTS, SOCKS5_IPV4_URL, SOCKS5_IPV4_URL_WO_AUTH, SOCKS5_IPV6_URL, SOCKS4_URL, HTTP_PROXY_URL, TEST_URL_IPV4, SOCKS5_IPV4_HOSTNAME_URL, TEST_URL_IPV4_HTTPS, HTTPS_PROXY_URL, ) from tests.mocks import getaddrinfo_async_mock trio = pytest.importorskip('trio') from python_socks.async_.trio._resolver import Resolver # noqa: E402 from python_socks.async_.trio.v2 import Proxy # noqa: E402 from python_socks.async_.trio.v2 import ProxyChain # noqa: E402 from python_socks.async_.trio.v2._proxy import TrioProxy # noqa: E402 async def make_request( proxy: TrioProxy, url: str, resolve_host=False, timeout=None, ssl_context=None, ): with patch( 'trio._highlevel_open_tcp_stream.getaddrinfo', new=getaddrinfo_async_mock(trio.socket.getaddrinfo), ): url = URL(url) dest_host = url.host if resolve_host: resolver = Resolver() _, dest_host = await resolver.resolve(url.host) if url.scheme == 'https': dest_ssl = ssl_context else: dest_ssl = None stream = await proxy.connect( dest_host=dest_host, dest_port=url.port, dest_ssl=dest_ssl, timeout=timeout, ) # fmt: off request = ( 'GET {rel_url} HTTP/1.1\r\n' 'Host: {host}\r\n' 'Connection: close\r\n\r\n' ) # fmt: on request = request.format(rel_url=url.path_qs, host=url.host) request = request.encode('ascii') await stream.write_all(request) response = await stream.read(1024) status_line = response.split(b'\r\n', 1)[0] version, status_code, *reason = status_line.split() await stream.close() return int(status_code) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) @pytest.mark.trio async def test_socks5_proxy_ipv4(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.trio async def test_socks5_proxy_hostname_ipv4(url, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_HOSTNAME_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.trio async def test_socks5_proxy_ipv4_with_auth_none(url, rdns, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL_WO_AUTH, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.trio async def test_socks5_proxy_with_invalid_credentials(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD + 'aaa', ) with pytest.raises(ProxyError): await make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.trio async def test_socks5_proxy_with_connect_timeout(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyTimeoutError): await make_request(proxy=proxy, url=TEST_URL_IPV4, timeout=0.0001) @pytest.mark.trio async def test_socks5_proxy_with_invalid_proxy_port(unused_tcp_port): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=unused_tcp_port, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyConnectionError): await make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.skipif(SKIP_IPV6_TESTS, reason="TravisCI doesn't support ipv6") @pytest.mark.trio async def test_socks5_proxy_ipv6(url, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV6_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) @pytest.mark.trio async def test_socks4_proxy(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS4_URL, rdns=rdns) status_code = await make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.trio async def test_http_proxy(url, target_ssl_context): proxy = Proxy.from_url(HTTP_PROXY_URL) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.trio async def test_secure_proxy(url, target_ssl_context, proxy_ssl_context): proxy = Proxy.from_url(HTTPS_PROXY_URL, proxy_ssl=proxy_ssl_context) status_code = await make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.trio async def test_proxy_chain(url, target_ssl_context): proxy = ProxyChain( [ Proxy.from_url(SOCKS5_IPV4_URL), Proxy.from_url(SOCKS4_URL), Proxy.from_url(HTTP_PROXY_URL), ] ) status_code = await make_request( proxy=proxy, # type: ignore url=url, ssl_context=target_ssl_context, ) assert status_code == 200 python-socks-2.7.1/tests/test_proxy_sync.py000066400000000000000000000137221474746205100211710ustar00rootroot00000000000000import socket from unittest import mock import pytest from yarl import URL from python_socks import ProxyType, ProxyError, ProxyTimeoutError, ProxyConnectionError from python_socks.sync import Proxy from python_socks.sync import ProxyChain from python_socks.sync._proxy import SyncProxy # noqa from python_socks.sync._resolver import SyncResolver # noqa from tests.config import ( PROXY_HOST_IPV4, SOCKS5_PROXY_PORT, LOGIN, PASSWORD, SKIP_IPV6_TESTS, SOCKS5_IPV4_URL, SOCKS5_IPV4_URL_WO_AUTH, SOCKS5_IPV6_URL, SOCKS4_URL, HTTP_PROXY_URL, HTTP_PROXY_PORT, TEST_URL_IPV4, TEST_URL_IPv6, SOCKS5_IPV4_HOSTNAME_URL, TEST_URL_IPV4_HTTPS, ) from tests.mocks import getaddrinfo_sync_mock def read_status_code(sock: socket.socket) -> int: data = sock.recv(1024) status_line = data.split(b'\r\n', 1)[0] status_line = status_line.decode('utf-8', 'surrogateescape') version, status_code, *reason = status_line.split() return int(status_code) def make_request( proxy: SyncProxy, url: str, resolve_host=False, timeout=None, ssl_context=None, ): with mock.patch('socket.getaddrinfo', new=getaddrinfo_sync_mock()): url = URL(url) dest_host = url.host if resolve_host: resolver = SyncResolver() _, dest_host = resolver.resolve(url.host) sock: socket.socket = proxy.connect( dest_host=dest_host, dest_port=url.port, timeout=timeout ) if url.scheme == 'https': assert ssl_context is not None sock = ssl_context.wrap_socket(sock=sock, server_hostname=url.host) request = 'GET {rel_url} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n' request = request.format(rel_url=url.path_qs, host=url.host) request = request.encode('ascii') sock.sendall(request) status_code = read_status_code(sock) sock.close() return status_code @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) def test_socks5_proxy_ipv4(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL, rdns=rdns) status_code = make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 def test_socks5_proxy_hostname_ipv4(): proxy = Proxy.from_url(SOCKS5_IPV4_HOSTNAME_URL) status_code = make_request( proxy=proxy, url=TEST_URL_IPV4, ) assert status_code == 200 @pytest.mark.parametrize('rdns', (None, True, False)) def test_socks5_proxy_ipv4_with_auth_none(rdns): proxy = Proxy.from_url(SOCKS5_IPV4_URL_WO_AUTH, rdns=rdns) status_code = make_request(proxy=proxy, url=TEST_URL_IPV4) assert status_code == 200 def test_socks5_proxy_with_invalid_credentials(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD + 'aaa', ) with pytest.raises(ProxyError): make_request(proxy=proxy, url=TEST_URL_IPV4) def test_socks5_proxy_with_connect_timeout(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyTimeoutError): make_request(proxy=proxy, url=TEST_URL_IPV4, timeout=0.001) def test_socks5_proxy_with_invalid_proxy_port(unused_tcp_port): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=unused_tcp_port, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyConnectionError): make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.skipif(SKIP_IPV6_TESTS, reason="TravisCI doesn't support ipv6") def test_socks5_proxy_ipv6(): proxy = Proxy.from_url(SOCKS5_IPV6_URL) status_code = make_request(proxy=proxy, url=TEST_URL_IPV4) assert status_code == 200 @pytest.mark.skipif(SKIP_IPV6_TESTS, reason="TravisCI doesn't support ipv6") @pytest.mark.parametrize('rdns', (True, False)) def test_socks5_proxy_hostname_ipv6(rdns): proxy = Proxy.from_url(SOCKS5_IPV4_URL, rdns=rdns) status_code = make_request(proxy=proxy, url=TEST_URL_IPv6) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) def test_socks4_proxy(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS4_URL, rdns=rdns) status_code = make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) def test_http_proxy(url, target_ssl_context): proxy = Proxy.from_url(HTTP_PROXY_URL) status_code = make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 def test_http_proxy_with_invalid_credentials(): proxy = Proxy.create( proxy_type=ProxyType.HTTP, host=PROXY_HOST_IPV4, port=HTTP_PROXY_PORT, username=LOGIN, password=PASSWORD + 'aaa', ) with pytest.raises(ProxyError): make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) def test_proxy_chain(url, target_ssl_context): proxy = ProxyChain( [ Proxy.from_url(SOCKS5_IPV4_URL), Proxy.from_url(SOCKS4_URL), Proxy.from_url(HTTP_PROXY_URL), ] ) status_code = make_request( proxy=proxy, # type: ignore url=url, ssl_context=target_ssl_context, ) assert status_code == 200 python-socks-2.7.1/tests/test_proxy_sync_v2.py000066400000000000000000000150621474746205100215770ustar00rootroot00000000000000import socket from typing import Union from unittest import mock import pytest from yarl import URL from python_socks import ProxyType, ProxyError, ProxyTimeoutError, ProxyConnectionError from python_socks.sync._resolver import SyncResolver from python_socks.sync.v2 import Proxy from python_socks.sync.v2 import ProxyChain from python_socks.sync.v2._proxy import SyncProxy from tests.config import ( PROXY_HOST_IPV4, SOCKS5_PROXY_PORT, LOGIN, PASSWORD, SKIP_IPV6_TESTS, SOCKS5_IPV4_URL, SOCKS5_IPV4_URL_WO_AUTH, SOCKS5_IPV6_URL, SOCKS4_URL, HTTP_PROXY_URL, HTTP_PROXY_PORT, TEST_URL_IPV4, TEST_URL_IPv6, SOCKS5_IPV4_HOSTNAME_URL, TEST_URL_IPV4_HTTPS, HTTPS_PROXY_URL, ) from tests.mocks import getaddrinfo_sync_mock def read_status_code(sock: socket.socket) -> int: data = sock.recv(1024) status_line = data.split(b'\r\n', 1)[0] status_line = status_line.decode('utf-8', 'surrogateescape') version, status_code, *reason = status_line.split() return int(status_code) def make_request( proxy: Union[SyncProxy, ProxyChain], url: str, resolve_host=False, timeout=None, ssl_context=None, ): with mock.patch('socket.getaddrinfo', new=getaddrinfo_sync_mock()): url = URL(url) dest_host = url.host if resolve_host: resolver = SyncResolver() _, dest_host = resolver.resolve(url.host) if url.scheme == 'https': dest_ssl = ssl_context else: dest_ssl = None stream = proxy.connect( dest_host=dest_host, dest_port=url.port, dest_ssl=dest_ssl, timeout=timeout, ) # fmt: off request = ( 'GET {rel_url} HTTP/1.1\r\n' 'Host: {host}\r\n' 'Connection: close\r\n\r\n' ) # fmt: on request = request.format(rel_url=url.path_qs, host=url.host) request = request.encode('ascii') stream.write_all(request) response = stream.read(1024) status_line = response.split(b'\r\n', 1)[0] version, status_code, *reason = status_line.split() stream.close() return int(status_code) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) def test_socks5_proxy_ipv4(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS5_IPV4_URL, rdns=rdns) status_code = make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 def test_socks5_proxy_hostname_ipv4(): proxy = Proxy.from_url(SOCKS5_IPV4_HOSTNAME_URL) status_code = make_request( proxy=proxy, url=TEST_URL_IPV4, ) assert status_code == 200 @pytest.mark.parametrize('rdns', (None, True, False)) def test_socks5_proxy_ipv4_with_auth_none(rdns): proxy = Proxy.from_url(SOCKS5_IPV4_URL_WO_AUTH, rdns=rdns) status_code = make_request(proxy=proxy, url=TEST_URL_IPV4) assert status_code == 200 def test_socks5_proxy_with_invalid_credentials(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD + 'aaa', ) with pytest.raises(ProxyError): make_request(proxy=proxy, url=TEST_URL_IPV4) def test_socks5_proxy_with_connect_timeout(): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=SOCKS5_PROXY_PORT, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyTimeoutError): make_request(proxy=proxy, url=TEST_URL_IPV4, timeout=0.001) def test_socks5_proxy_with_invalid_proxy_port(unused_tcp_port): proxy = Proxy.create( proxy_type=ProxyType.SOCKS5, host=PROXY_HOST_IPV4, port=unused_tcp_port, username=LOGIN, password=PASSWORD, ) with pytest.raises(ProxyConnectionError): make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.skipif(SKIP_IPV6_TESTS, reason="TravisCI doesn't support ipv6") def test_socks5_proxy_ipv6(): proxy = Proxy.from_url(SOCKS5_IPV6_URL) status_code = make_request(proxy=proxy, url=TEST_URL_IPV4) assert status_code == 200 @pytest.mark.skipif(SKIP_IPV6_TESTS, reason="TravisCI doesn't support ipv6") @pytest.mark.parametrize('rdns', (True, False)) def test_socks5_proxy_hostname_ipv6(rdns): proxy = Proxy.from_url(SOCKS5_IPV4_URL, rdns=rdns) status_code = make_request(proxy=proxy, url=TEST_URL_IPv6) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) @pytest.mark.parametrize('rdns', (None, True, False)) @pytest.mark.parametrize('resolve_host', (True, False)) def test_socks4_proxy(url, rdns, resolve_host, target_ssl_context): proxy = Proxy.from_url(SOCKS4_URL, rdns=rdns) status_code = make_request( proxy=proxy, url=url, resolve_host=resolve_host, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) def test_http_proxy(url, target_ssl_context): proxy = Proxy.from_url(HTTP_PROXY_URL) status_code = make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) def test_secure_proxy(url, target_ssl_context, proxy_ssl_context): proxy = Proxy.from_url(HTTPS_PROXY_URL, proxy_ssl=proxy_ssl_context) status_code = make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 def test_http_proxy_with_invalid_credentials(): proxy = Proxy.create( proxy_type=ProxyType.HTTP, host=PROXY_HOST_IPV4, port=HTTP_PROXY_PORT, username=LOGIN, password=PASSWORD + 'aaa', ) with pytest.raises(ProxyError): make_request(proxy=proxy, url=TEST_URL_IPV4) @pytest.mark.parametrize('url', (TEST_URL_IPV4, TEST_URL_IPV4_HTTPS)) def test_proxy_chain(url, target_ssl_context): proxy = ProxyChain( [ Proxy.from_url(SOCKS5_IPV4_URL), Proxy.from_url(SOCKS4_URL), Proxy.from_url(HTTP_PROXY_URL), ] ) status_code = make_request( proxy=proxy, url=url, ssl_context=target_ssl_context, ) assert status_code == 200 python-socks-2.7.1/tests/test_resolvers.py000066400000000000000000000054221474746205100207760ustar00rootroot00000000000000import socket from unittest.mock import MagicMock, patch import pytest from python_socks.async_.asyncio._resolver import Resolver as AsyncioResolver from python_socks.sync._resolver import SyncResolver RET_FAMILY = socket.AF_INET RET_HOST = '127.0.0.1' RET_VALUE = [( RET_FAMILY, socket.SOCK_STREAM, 6, '', (RET_HOST, 0) )] async def get_value_async(): return RET_VALUE TEST_HOST_NAME = 'fake.host.name' @patch('socket.getaddrinfo', return_value=RET_VALUE) def test_sync_resolver_1(_): resolver = SyncResolver() family, host = resolver.resolve(host=TEST_HOST_NAME) assert family == RET_FAMILY assert host == RET_HOST @patch('socket.getaddrinfo', return_value=[]) def test_sync_resolver_2(_): with pytest.raises(OSError): resolver = SyncResolver() resolver.resolve(host=TEST_HOST_NAME) @pytest.mark.asyncio async def test_asyncio_resolver(): loop = MagicMock() loop.getaddrinfo = MagicMock() loop.getaddrinfo.return_value = get_value_async() resolver = AsyncioResolver(loop) family, host = await resolver.resolve(host=TEST_HOST_NAME) assert family == RET_FAMILY assert host == RET_HOST @pytest.mark.trio async def test_trio_resolver(): pytest.importorskip('trio') from python_socks.async_.trio._resolver import Resolver as TrioResolver getaddrinfo = MagicMock() getaddrinfo.return_value = get_value_async() # with patch('trio.socket.getaddrinfo', return_value=get_value_async()): with patch('trio.socket.getaddrinfo', new=getaddrinfo): resolver = TrioResolver() family, host = await resolver.resolve(host=TEST_HOST_NAME) assert family == RET_FAMILY assert host == RET_HOST @pytest.mark.anyio async def test_anyio_resolver(): pytest.importorskip('anyio') from python_socks.async_.anyio._resolver import Resolver as AnyioResolver getaddrinfo = MagicMock() getaddrinfo.return_value = get_value_async() with patch('anyio.getaddrinfo', new=getaddrinfo): resolver = AnyioResolver() family, host = await resolver.resolve(host=TEST_HOST_NAME) assert family == RET_FAMILY assert host == RET_HOST def test_curio_resolver(): curio = pytest.importorskip('curio') from python_socks.async_.curio._resolver import Resolver as CurioResolver getaddrinfo = MagicMock() getaddrinfo.return_value = get_value_async() to_patch = 'python_socks.async_.curio._resolver.getaddrinfo' async def run(): # with patch(to_patch, return_value=get_value_async()): with patch(to_patch, new=getaddrinfo): resolver = CurioResolver() family, host = await resolver.resolve(host=TEST_HOST_NAME) assert family == RET_FAMILY assert host == RET_HOST curio.run(run) python-socks-2.7.1/tests/utils.py000066400000000000000000000011211474746205100170430ustar00rootroot00000000000000import socket import time def is_connectable(host, port): try: sock = socket.create_connection((host, port), 1) except socket.error: return False else: sock.close() return True def wait_until_connectable(host, port, timeout=10): count = 0 while not is_connectable(host=host, port=port): if count >= timeout: raise Exception( f'The proxy server has not available by ({host}, {port}) in {timeout:d} seconds' ) count += 1 time.sleep(1) return True