Compare commits
13 Commits
66362780a0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb8d43841c | ||
|
|
6a68d5b66a | ||
|
|
aaf46207a4 | ||
|
|
d5f1b2ae77 | ||
|
|
1a339d82cd | ||
|
|
29c473890a | ||
|
|
3b3fac1cee | ||
|
|
9f0e2a2db2 | ||
|
|
31cac56fad | ||
|
|
0e9bdf0052 | ||
|
|
19d8ad3721 | ||
|
|
15c9e1772a | ||
|
|
8b62c445fc |
7
.env
7
.env
@@ -6,9 +6,12 @@ APIKEY=sk-85880595fc714d63bfd0b025e917bd26#千问apikey
|
||||
# 962516e4-60eb-4a26-a5a3-44e21adcf7bc #豆包
|
||||
|
||||
# 消息回调(ngrok 调通用,由 run-ngrok.sh 自动写入)
|
||||
CALLBACK_BASE_URL=https://dissonant-destinee-nonsensibly.ngrok-free.dev
|
||||
# CALLBACK_BASE_URL=https://dissonant-destinee-nonsensibly.ngrok-free.dev
|
||||
|
||||
CALLBACK_BASE_URL=http://demo.bimwe.com
|
||||
|
||||
# 固定隧道代理(socks5h):不填登录页代理时后端自动用此处,传给 7006
|
||||
TUNNEL_PROXY=218.78.109.253:16816
|
||||
# TUNNEL_PROXY=218.78.109.253:16816
|
||||
TUNNEL_PROXY=14.103.95.78:16816
|
||||
TUNNEL_PROXY_USERNAME=fawbjjkk
|
||||
TUNNEL_PROXY_PASSWORD=5hz6avfb
|
||||
@@ -4,6 +4,10 @@ PORT=3000
|
||||
# BACKEND_HOST=127.0.0.1
|
||||
WECHAT_UPSTREAM_BASE_URL=http://113.44.162.180:7006
|
||||
CHECK_STATUS_BASE_URL=http://113.44.162.180:7006
|
||||
# 群发图片:默认用 7006 /message/SendImageMessage,MsgItem.MsgType=0;可覆盖
|
||||
# SEND_IMAGE_UPSTREAM_BASE_URL=http://113.44.162.180:7006
|
||||
# SEND_IMAGE_PATH=/message/SendImageMessage
|
||||
# IMAGE_MSG_TYPE=0
|
||||
# 第三方滑块(7765):iframe 加载自带预填表单页,提交到下方地址
|
||||
SLIDER_VERIFY_BASE_URL=http://113.44.162.180:7765
|
||||
SLIDER_VERIFY_KEY=408449830
|
||||
@@ -16,7 +20,7 @@ SLIDER_VERIFY_KEY=408449830
|
||||
|
||||
# 固定隧道代理(推荐):socks5h + 用户名密码,与 requests 示例一致
|
||||
# 不填登录页代理时,后端自动使用此处配置
|
||||
TUNNEL_PROXY=218.78.109.253:16816
|
||||
TUNNEL_PROXY=14.103.95.78:16816
|
||||
TUNNEL_PROXY_USERNAME=fawbjjkk
|
||||
TUNNEL_PROXY_PASSWORD=5hz6avfb
|
||||
|
||||
@@ -32,6 +36,7 @@ TUNNEL_PROXY_PASSWORD=5hz6avfb
|
||||
|
||||
# 消息实时回调(主入口):设置后向 7006 注册 SetCallback,新消息由 7006 POST 到本服务,不再走 WS
|
||||
# 需为 7006 能访问到的公网地址,例如 https://your-domain.com
|
||||
# 项目启动时会自动为所有已知账号 key 重新注册回调,重启后无需再手动设置
|
||||
# CALLBACK_BASE_URL=https://your-domain.com
|
||||
|
||||
# 千问 API Key(用于个性化问候等),优先 QWEN_API_KEY,其次 APIKEY
|
||||
|
||||
22
.env.prod.example
Normal file
22
.env.prod.example
Normal file
@@ -0,0 +1,22 @@
|
||||
# 生产环境变量模板(随项目提交)
|
||||
# 部署时复制为 .env.prod 并填写真实值:cp .env.prod.example .env.prod && 编辑 .env.prod
|
||||
# run-docker.sh 仅读取 .env.prod,不会读取 .env(本地开发用)
|
||||
|
||||
PORT=3003
|
||||
BACKEND_PORT=8008
|
||||
|
||||
WECHAT_UPSTREAM_BASE_URL=http://113.44.162.180:7006
|
||||
WS_KEY=HBpEnbtj9BJZ
|
||||
SLIDER_VERIFY_KEY=408449830
|
||||
APIKEY=sk-85880595fc714d63bfd0b025e917bd26#千问apikey
|
||||
# 962516e4-60eb-4a26-a5a3-44e21adcf7bc #豆包
|
||||
|
||||
# 消息回调(ngrok 调通用,由 run-ngrok.sh 自动写入)
|
||||
CALLBACK_BASE_URL=http://demo.bimwe.com/
|
||||
|
||||
|
||||
# 固定隧道代理(socks5h):不填登录页代理时后端自动用此处,传给 7006
|
||||
# TUNNEL_PROXY=218.78.109.253:16816
|
||||
TUNNEL_PROXY=14.103.95.78:16816
|
||||
TUNNEL_PROXY_USERNAME=fawbjjkk
|
||||
TUNNEL_PROXY_PASSWORD=5hz6avfb
|
||||
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# 本地环境(含密钥,不提交)
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# 生产环境(服务器上单独维护,不提交)
|
||||
.env.prod
|
||||
|
||||
# 保留模板(随项目提交)
|
||||
# .env.example
|
||||
# .env.prod.example
|
||||
|
||||
# 依赖与构建
|
||||
node_modules/
|
||||
.venv/
|
||||
venv/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
dist/
|
||||
build/
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
|
||||
# 数据与日志(可挂载卷)
|
||||
backend/data/
|
||||
data/
|
||||
*.db
|
||||
*.sqlite
|
||||
*.log
|
||||
|
||||
# 系统与编辑器
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# 调试与临时
|
||||
*.tmp
|
||||
.cache/
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,201 @@
|
||||
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.
|
||||
@@ -0,0 +1,101 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: httpx-socks
|
||||
Version: 0.9.2
|
||||
Summary: Proxy (HTTP, SOCKS) transports for httpx
|
||||
Home-page: https://github.com/romis2012/httpx-socks
|
||||
Author: Roman Snegirev
|
||||
Author-email: snegiryev@gmail.com
|
||||
License: Apache 2
|
||||
Keywords: httpx asyncio socks socks5 socks4 http proxy
|
||||
Description-Content-Type: text/markdown
|
||||
License-File: LICENSE.txt
|
||||
Requires-Dist: httpx<0.28.0,>=0.21.0
|
||||
Requires-Dist: httpcore<2.0,>=0.17.3
|
||||
Requires-Dist: python-socks>=2.0.0
|
||||
Provides-Extra: asyncio
|
||||
Requires-Dist: async-timeout>=3.0.1; extra == "asyncio"
|
||||
Provides-Extra: trio
|
||||
Requires-Dist: trio>=0.16.0; extra == "trio"
|
||||
|
||||
# httpx-socks
|
||||
|
||||
[](https://github.com/romis2012/httpx-socks/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/romis2012/httpx-socks)
|
||||
[](https://pypi.python.org/pypi/httpx-socks)
|
||||
<!--
|
||||
[](https://pepy.tech/project/httpx-socks)
|
||||
-->
|
||||
|
||||
The `httpx-socks` package provides proxy transports for [httpx](https://github.com/encode/httpx) client.
|
||||
SOCKS4(a), SOCKS5(h), HTTP (tunneling) proxy supported.
|
||||
It uses [python-socks](https://github.com/romis2012/python-socks) for core proxy functionality.
|
||||
|
||||
|
||||
## Requirements
|
||||
- Python >= 3.6
|
||||
- httpx>=0.21.0
|
||||
- python-socks>=2.0.0
|
||||
- async-timeout>=3.0.1 (optional)
|
||||
- trio>=0.16.0 (optional)
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
only sync proxy support:
|
||||
```
|
||||
pip install httpx-socks
|
||||
```
|
||||
|
||||
to include optional asyncio support (it requires async-timeout):
|
||||
```
|
||||
pip install httpx-socks[asyncio]
|
||||
```
|
||||
|
||||
to include optional trio support:
|
||||
```
|
||||
pip install httpx-socks[trio]
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
#### sync transport
|
||||
```python
|
||||
import httpx
|
||||
from httpx_socks import SyncProxyTransport
|
||||
|
||||
def fetch(url):
|
||||
transport = SyncProxyTransport.from_url('socks5://user:password@127.0.0.1:1080')
|
||||
with httpx.Client(transport=transport) as client:
|
||||
res = client.get(url)
|
||||
return res.text
|
||||
```
|
||||
|
||||
#### async transport (asyncio, trio)
|
||||
```python
|
||||
import httpx
|
||||
from httpx_socks import AsyncProxyTransport
|
||||
|
||||
async def fetch(url):
|
||||
transport = AsyncProxyTransport.from_url('socks5://user:password@127.0.0.1:1080')
|
||||
async with httpx.AsyncClient(transport=transport) as client:
|
||||
res = await client.get(url)
|
||||
return res.text
|
||||
```
|
||||
|
||||
#### secure proxy connections (aka "HTTPS proxies", experimental feature, both sync and async support)
|
||||
```python
|
||||
import ssl
|
||||
import httpx
|
||||
from httpx_socks import AsyncProxyTransport
|
||||
|
||||
async def fetch(url):
|
||||
proxy_ssl = ssl.SSLContext(ssl.PROTOCOL_TLS)
|
||||
proxy_ssl.verify_mode = ssl.CERT_REQUIRED
|
||||
proxy_ssl.load_verify_locations(...)
|
||||
|
||||
transport = AsyncProxyTransport.from_url('http://user:password@127.0.0.1:8080', proxy_ssl=proxy_ssl)
|
||||
async with httpx.AsyncClient(transport=transport) as client:
|
||||
res = await client.get(url)
|
||||
return res.text
|
||||
```
|
||||
@@ -0,0 +1,19 @@
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/httpx_socks/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/httpx_socks/_async_proxy.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/httpx_socks/_async_transport.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/httpx_socks/_sync_proxy.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/httpx_socks/_sync_stream.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/httpx_socks/_sync_transport.cpython-39.pyc,,
|
||||
httpx_socks-0.9.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
httpx_socks-0.9.2.dist-info/LICENSE.txt,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
||||
httpx_socks-0.9.2.dist-info/METADATA,sha256=nYzxhiQ-G1XYBKP58owrMZWfJhGYCm6aKh7G4rn26zM,3007
|
||||
httpx_socks-0.9.2.dist-info/RECORD,,
|
||||
httpx_socks-0.9.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
httpx_socks-0.9.2.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
||||
httpx_socks-0.9.2.dist-info/top_level.txt,sha256=w-NjPGd_PneJsrpI5kELbCCdbD3YlL8mgwP3WkawJ0s,12
|
||||
httpx_socks/__init__.py,sha256=_iMI2My4TRAD66ZQgHrM3-CVQ8M-gGzLGVBibPF6hl0,448
|
||||
httpx_socks/_async_proxy.py,sha256=RqnPas10Ux-VRACZu2qZsXsCxF-a5ZoeSw1VLmaSPYc,8452
|
||||
httpx_socks/_async_transport.py,sha256=QEcg3Pb0hvMvcebKbolMdhrfdVuO1m-V_9-PrFOPF9A,3150
|
||||
httpx_socks/_sync_proxy.py,sha256=vMVrR2-qKxmRHhn8lKFXXz-dDolAZIi96PeBp4YQF3g,6544
|
||||
httpx_socks/_sync_stream.py,sha256=aaAH6bSPkAdHeDf2rvNDx_-lbu4kXFg1u2OMBNzQ3QQ,1421
|
||||
httpx_socks/_sync_transport.py,sha256=FiqT2gekBI3V1Jq_uoFRa7biksLDljcEPrNFPq0LX-c,3054
|
||||
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (75.3.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
httpx_socks
|
||||
23
.venv/lib/python3.9/site-packages/httpx_socks/__init__.py
Normal file
23
.venv/lib/python3.9/site-packages/httpx_socks/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
__title__ = 'httpx-socks'
|
||||
__version__ = '0.9.2'
|
||||
|
||||
from python_socks import (
|
||||
ProxyError,
|
||||
ProxyTimeoutError,
|
||||
ProxyConnectionError,
|
||||
ProxyType
|
||||
)
|
||||
|
||||
from ._sync_transport import SyncProxyTransport
|
||||
from ._async_transport import AsyncProxyTransport
|
||||
|
||||
__all__ = (
|
||||
'__title__',
|
||||
'__version__',
|
||||
'SyncProxyTransport',
|
||||
'AsyncProxyTransport',
|
||||
'ProxyError',
|
||||
'ProxyTimeoutError',
|
||||
'ProxyConnectionError',
|
||||
'ProxyType',
|
||||
)
|
||||
258
.venv/lib/python3.9/site-packages/httpx_socks/_async_proxy.py
Normal file
258
.venv/lib/python3.9/site-packages/httpx_socks/_async_proxy.py
Normal file
@@ -0,0 +1,258 @@
|
||||
import ssl
|
||||
|
||||
import sniffio
|
||||
from httpcore import (
|
||||
AsyncConnectionPool,
|
||||
Origin,
|
||||
AsyncConnectionInterface,
|
||||
Request,
|
||||
Response,
|
||||
default_ssl_context,
|
||||
AsyncHTTP11Connection,
|
||||
ConnectionNotAvailable,
|
||||
)
|
||||
from httpcore import AsyncNetworkStream
|
||||
from httpcore._synchronization import AsyncLock
|
||||
from python_socks import ProxyType, parse_proxy_url
|
||||
|
||||
|
||||
class AsyncProxy(AsyncConnectionPool):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
proxy_type: ProxyType,
|
||||
proxy_host: str,
|
||||
proxy_port: int,
|
||||
username=None,
|
||||
password=None,
|
||||
rdns=None,
|
||||
proxy_ssl: ssl.SSLContext = None,
|
||||
loop=None,
|
||||
**kwargs,
|
||||
):
|
||||
self._proxy_type = proxy_type
|
||||
self._proxy_host = proxy_host
|
||||
self._proxy_port = proxy_port
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._rdns = rdns
|
||||
self._proxy_ssl = proxy_ssl
|
||||
self._loop = loop
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def create_connection(self, origin: Origin) -> AsyncConnectionInterface:
|
||||
return AsyncProxyConnection(
|
||||
proxy_type=self._proxy_type,
|
||||
proxy_host=self._proxy_host,
|
||||
proxy_port=self._proxy_port,
|
||||
username=self._username,
|
||||
password=self._password,
|
||||
rdns=self._rdns,
|
||||
proxy_ssl=self._proxy_ssl,
|
||||
loop=self._loop,
|
||||
remote_origin=origin,
|
||||
ssl_context=self._ssl_context,
|
||||
keepalive_expiry=self._keepalive_expiry,
|
||||
http1=self._http1,
|
||||
http2=self._http2,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_url(cls, url, **kwargs):
|
||||
proxy_type, host, port, username, password = parse_proxy_url(url)
|
||||
return cls(
|
||||
proxy_type=proxy_type,
|
||||
proxy_host=host,
|
||||
proxy_port=port,
|
||||
username=username,
|
||||
password=password,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class AsyncProxyConnection(AsyncConnectionInterface):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
proxy_type: ProxyType,
|
||||
proxy_host: str,
|
||||
proxy_port: int,
|
||||
username=None,
|
||||
password=None,
|
||||
rdns=None,
|
||||
proxy_ssl: ssl.SSLContext = None,
|
||||
loop=None,
|
||||
remote_origin: Origin,
|
||||
ssl_context: ssl.SSLContext,
|
||||
keepalive_expiry: float = None,
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
) -> None:
|
||||
|
||||
if ssl_context is None: # pragma: no cover
|
||||
ssl_context = default_ssl_context()
|
||||
|
||||
self._proxy_type = proxy_type
|
||||
self._proxy_host = proxy_host
|
||||
self._proxy_port = proxy_port
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._rdns = rdns
|
||||
self._proxy_ssl = proxy_ssl
|
||||
self._loop = loop
|
||||
|
||||
self._remote_origin = remote_origin
|
||||
self._ssl_context = ssl_context
|
||||
self._keepalive_expiry = keepalive_expiry
|
||||
self._http1 = http1
|
||||
self._http2 = http2
|
||||
|
||||
self._connect_lock = AsyncLock()
|
||||
self._connection = None
|
||||
self._connect_failed: bool = False
|
||||
|
||||
async def handle_async_request(self, request: Request) -> Response:
|
||||
timeouts = request.extensions.get('timeout', {})
|
||||
timeout = timeouts.get('connect', None)
|
||||
|
||||
try:
|
||||
async with self._connect_lock:
|
||||
if self._connection is None:
|
||||
stream = await self._connect_via_proxy(
|
||||
origin=self._remote_origin,
|
||||
connect_timeout=timeout,
|
||||
)
|
||||
|
||||
ssl_object = stream.get_extra_info("ssl_object")
|
||||
http2_negotiated = (
|
||||
ssl_object is not None and ssl_object.selected_alpn_protocol() == "h2"
|
||||
)
|
||||
if http2_negotiated or (self._http2 and not self._http1):
|
||||
from httpcore import AsyncHTTP2Connection
|
||||
|
||||
self._connection = AsyncHTTP2Connection(
|
||||
origin=self._remote_origin,
|
||||
stream=stream,
|
||||
keepalive_expiry=self._keepalive_expiry,
|
||||
)
|
||||
else:
|
||||
self._connection = AsyncHTTP11Connection(
|
||||
origin=self._remote_origin,
|
||||
stream=stream,
|
||||
keepalive_expiry=self._keepalive_expiry,
|
||||
)
|
||||
elif not self._connection.is_available(): # pragma: no cover
|
||||
raise ConnectionNotAvailable()
|
||||
except BaseException as exc:
|
||||
self._connect_failed = True
|
||||
raise exc
|
||||
|
||||
return await self._connection.handle_async_request(request)
|
||||
|
||||
async def _connect_via_proxy(self, origin, connect_timeout) -> AsyncNetworkStream:
|
||||
scheme, hostname, port = origin.scheme, origin.host, origin.port
|
||||
|
||||
ssl_context = self._ssl_context if scheme == b'https' else None
|
||||
host = hostname.decode('ascii') # ?
|
||||
|
||||
return await self._open_stream(
|
||||
host=host,
|
||||
port=port,
|
||||
connect_timeout=connect_timeout,
|
||||
ssl_context=ssl_context,
|
||||
)
|
||||
|
||||
async def _open_stream(self, host, port, connect_timeout, ssl_context):
|
||||
backend = sniffio.current_async_library()
|
||||
|
||||
if backend == 'asyncio':
|
||||
return await self._open_aio_stream(host, port, connect_timeout, ssl_context)
|
||||
|
||||
if backend == 'trio':
|
||||
return await self._open_trio_stream(host, port, connect_timeout, ssl_context)
|
||||
|
||||
# Curio support has been dropped in httpcore 0.14.0
|
||||
# if backend == 'curio':
|
||||
# return await self._open_curio_stream(host, port, connect_timeout, ssl_context)
|
||||
|
||||
raise RuntimeError(f'Unsupported concurrency backend {backend!r}') # pragma: no cover
|
||||
|
||||
async def _open_aio_stream(self, host, port, connect_timeout, ssl_context):
|
||||
from httpcore._backends.anyio import AnyIOStream
|
||||
from python_socks.async_.anyio import Proxy
|
||||
|
||||
proxy = Proxy.create(
|
||||
proxy_type=self._proxy_type,
|
||||
host=self._proxy_host,
|
||||
port=self._proxy_port,
|
||||
username=self._username,
|
||||
password=self._password,
|
||||
rdns=self._rdns,
|
||||
proxy_ssl=self._proxy_ssl,
|
||||
)
|
||||
|
||||
proxy_stream = await proxy.connect(
|
||||
host,
|
||||
port,
|
||||
dest_ssl=ssl_context,
|
||||
timeout=connect_timeout,
|
||||
)
|
||||
|
||||
return AnyIOStream(proxy_stream.anyio_stream)
|
||||
|
||||
async def _open_trio_stream(self, host, port, connect_timeout, ssl_context):
|
||||
from httpcore._backends.trio import TrioStream
|
||||
from python_socks.async_.trio.v2 import Proxy
|
||||
|
||||
proxy = Proxy.create(
|
||||
proxy_type=self._proxy_type,
|
||||
host=self._proxy_host,
|
||||
port=self._proxy_port,
|
||||
username=self._username,
|
||||
password=self._password,
|
||||
rdns=self._rdns,
|
||||
proxy_ssl=self._proxy_ssl,
|
||||
)
|
||||
|
||||
proxy_stream = await proxy.connect(
|
||||
host,
|
||||
port,
|
||||
dest_ssl=ssl_context,
|
||||
timeout=connect_timeout,
|
||||
)
|
||||
|
||||
return TrioStream(proxy_stream.trio_stream)
|
||||
|
||||
async def aclose(self) -> None:
|
||||
if self._connection is not None:
|
||||
await self._connection.aclose()
|
||||
|
||||
def can_handle_request(self, origin: Origin) -> bool:
|
||||
return origin == self._remote_origin
|
||||
|
||||
def is_available(self) -> bool:
|
||||
if self._connection is None: # pragma: no cover
|
||||
# return self._http2 and (self._remote_origin.scheme == b"https" or not self._http1)
|
||||
return False
|
||||
return self._connection.is_available()
|
||||
|
||||
def has_expired(self) -> bool:
|
||||
if self._connection is None:
|
||||
return self._connect_failed
|
||||
return self._connection.has_expired()
|
||||
|
||||
def is_idle(self) -> bool:
|
||||
if self._connection is None:
|
||||
return self._connect_failed
|
||||
return self._connection.is_idle()
|
||||
|
||||
def is_closed(self) -> bool:
|
||||
if self._connection is None:
|
||||
return self._connect_failed
|
||||
return self._connection.is_closed()
|
||||
|
||||
def info(self) -> str: # pragma: no cover
|
||||
if self._connection is None:
|
||||
return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
|
||||
return self._connection.info()
|
||||
@@ -0,0 +1,104 @@
|
||||
import ssl
|
||||
import typing
|
||||
|
||||
import httpcore
|
||||
from httpx import AsyncBaseTransport, Request, Response, AsyncByteStream, Limits
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
from httpx._config import DEFAULT_LIMITS, create_ssl_context
|
||||
# noinspection PyProtectedMember
|
||||
from httpx._transports.default import AsyncResponseStream, map_httpcore_exceptions
|
||||
from python_socks import ProxyType, parse_proxy_url
|
||||
|
||||
from ._async_proxy import AsyncProxy
|
||||
|
||||
|
||||
class AsyncProxyTransport(AsyncBaseTransport):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
proxy_type: ProxyType,
|
||||
proxy_host: str,
|
||||
proxy_port: int,
|
||||
username=None,
|
||||
password=None,
|
||||
rdns=None,
|
||||
proxy_ssl: ssl.SSLContext = None,
|
||||
verify=True,
|
||||
cert=None,
|
||||
trust_env: bool = True,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
**kwargs,
|
||||
):
|
||||
ssl_context = create_ssl_context(
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
trust_env=trust_env,
|
||||
http2=kwargs.get('http2', False),
|
||||
)
|
||||
|
||||
self._pool = AsyncProxy(
|
||||
proxy_type=proxy_type,
|
||||
proxy_host=proxy_host,
|
||||
proxy_port=proxy_port,
|
||||
username=username,
|
||||
password=password,
|
||||
rdns=rdns,
|
||||
proxy_ssl=proxy_ssl,
|
||||
ssl_context=ssl_context,
|
||||
max_connections=limits.max_connections,
|
||||
max_keepalive_connections=limits.max_keepalive_connections,
|
||||
keepalive_expiry=limits.keepalive_expiry,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
async def handle_async_request(self, request: Request) -> Response:
|
||||
assert isinstance(request.stream, AsyncByteStream)
|
||||
|
||||
req = httpcore.Request(
|
||||
method=request.method,
|
||||
url=httpcore.URL(
|
||||
scheme=request.url.raw_scheme,
|
||||
host=request.url.raw_host,
|
||||
port=request.url.port,
|
||||
target=request.url.raw_path,
|
||||
),
|
||||
headers=request.headers.raw,
|
||||
content=request.stream,
|
||||
extensions=request.extensions,
|
||||
)
|
||||
|
||||
with map_httpcore_exceptions():
|
||||
resp = await self._pool.handle_async_request(req)
|
||||
|
||||
assert isinstance(resp.stream, typing.AsyncIterable)
|
||||
|
||||
return Response(
|
||||
status_code=resp.status,
|
||||
headers=resp.headers,
|
||||
stream=AsyncResponseStream(resp.stream),
|
||||
extensions=resp.extensions,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_url(cls, url, **kwargs):
|
||||
proxy_type, host, port, username, password = parse_proxy_url(url)
|
||||
return cls(
|
||||
proxy_type=proxy_type,
|
||||
proxy_host=host,
|
||||
proxy_port=port,
|
||||
username=username,
|
||||
password=password,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
async def aclose(self) -> None:
|
||||
await self._pool.aclose() # pragma: no cover
|
||||
|
||||
async def __aenter__(self):
|
||||
await self._pool.__aenter__()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type=None, exc_value=None, traceback=None):
|
||||
with map_httpcore_exceptions():
|
||||
await self._pool.__aexit__(exc_type, exc_value, traceback)
|
||||
206
.venv/lib/python3.9/site-packages/httpx_socks/_sync_proxy.py
Normal file
206
.venv/lib/python3.9/site-packages/httpx_socks/_sync_proxy.py
Normal file
@@ -0,0 +1,206 @@
|
||||
import ssl
|
||||
|
||||
from httpcore import (
|
||||
ConnectionPool,
|
||||
Origin,
|
||||
ConnectionInterface,
|
||||
Request,
|
||||
Response,
|
||||
default_ssl_context,
|
||||
HTTP11Connection,
|
||||
ConnectionNotAvailable,
|
||||
)
|
||||
# from httpcore.backends.sync import SyncStream
|
||||
from ._sync_stream import SyncStream
|
||||
from httpcore._synchronization import Lock
|
||||
|
||||
from python_socks import ProxyType, parse_proxy_url
|
||||
from python_socks.sync.v2 import Proxy
|
||||
|
||||
|
||||
class SyncProxy(ConnectionPool):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
proxy_type: ProxyType,
|
||||
proxy_host: str,
|
||||
proxy_port: int,
|
||||
username=None,
|
||||
password=None,
|
||||
rdns=None,
|
||||
proxy_ssl: ssl.SSLContext = None,
|
||||
**kwargs,
|
||||
):
|
||||
self._proxy_type = proxy_type
|
||||
self._proxy_host = proxy_host
|
||||
self._proxy_port = proxy_port
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._rdns = rdns
|
||||
self._proxy_ssl = proxy_ssl
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def create_connection(self, origin: Origin) -> ConnectionInterface:
|
||||
return SyncProxyConnection(
|
||||
proxy_type=self._proxy_type,
|
||||
proxy_host=self._proxy_host,
|
||||
proxy_port=self._proxy_port,
|
||||
username=self._username,
|
||||
password=self._password,
|
||||
rdns=self._rdns,
|
||||
proxy_ssl=self._proxy_ssl,
|
||||
remote_origin=origin,
|
||||
ssl_context=self._ssl_context,
|
||||
keepalive_expiry=self._keepalive_expiry,
|
||||
http1=self._http1,
|
||||
http2=self._http2,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_url(cls, url, **kwargs):
|
||||
proxy_type, host, port, username, password = parse_proxy_url(url)
|
||||
return cls(
|
||||
proxy_type=proxy_type,
|
||||
proxy_host=host,
|
||||
proxy_port=port,
|
||||
username=username,
|
||||
password=password,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class SyncProxyConnection(ConnectionInterface):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
proxy_type: ProxyType,
|
||||
proxy_host: str,
|
||||
proxy_port: int,
|
||||
username=None,
|
||||
password=None,
|
||||
rdns=None,
|
||||
proxy_ssl: ssl.SSLContext = None,
|
||||
remote_origin: Origin,
|
||||
ssl_context: ssl.SSLContext,
|
||||
keepalive_expiry: float = None,
|
||||
http1: bool = True,
|
||||
http2: bool = False,
|
||||
) -> None:
|
||||
|
||||
if ssl_context is None: # pragma: no cover
|
||||
ssl_context = default_ssl_context()
|
||||
|
||||
self._proxy_type = proxy_type
|
||||
self._proxy_host = proxy_host
|
||||
self._proxy_port = proxy_port
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._rdns = rdns
|
||||
self._proxy_ssl = proxy_ssl
|
||||
|
||||
self._remote_origin = remote_origin
|
||||
self._ssl_context = ssl_context
|
||||
self._keepalive_expiry = keepalive_expiry
|
||||
self._http1 = http1
|
||||
self._http2 = http2
|
||||
|
||||
self._connect_lock = Lock()
|
||||
self._connection = None
|
||||
self._connect_failed: bool = False
|
||||
|
||||
def handle_request(self, request: Request) -> Response:
|
||||
timeouts = request.extensions.get('timeout', {})
|
||||
timeout = timeouts.get('connect', None)
|
||||
|
||||
try:
|
||||
with self._connect_lock:
|
||||
if self._connection is None:
|
||||
stream = self._connect_via_proxy(
|
||||
origin=self._remote_origin,
|
||||
connect_timeout=timeout,
|
||||
)
|
||||
|
||||
ssl_object = stream.get_extra_info('ssl_object')
|
||||
http2_negotiated = (
|
||||
ssl_object is not None and ssl_object.selected_alpn_protocol() == "h2"
|
||||
)
|
||||
if http2_negotiated or (self._http2 and not self._http1):
|
||||
from httpcore import HTTP2Connection
|
||||
|
||||
self._connection = HTTP2Connection(
|
||||
origin=self._remote_origin,
|
||||
stream=stream,
|
||||
keepalive_expiry=self._keepalive_expiry,
|
||||
)
|
||||
else:
|
||||
self._connection = HTTP11Connection(
|
||||
origin=self._remote_origin,
|
||||
stream=stream,
|
||||
keepalive_expiry=self._keepalive_expiry,
|
||||
)
|
||||
elif not self._connection.is_available(): # pragma: no cover
|
||||
raise ConnectionNotAvailable()
|
||||
except BaseException as exc:
|
||||
self._connect_failed = True
|
||||
raise exc
|
||||
|
||||
return self._connection.handle_request(request)
|
||||
|
||||
def _connect_via_proxy(self, origin: Origin, connect_timeout: int):
|
||||
scheme, hostname, port = origin.scheme, origin.host, origin.port
|
||||
|
||||
ssl_context = self._ssl_context if scheme == b'https' else None
|
||||
host = hostname.decode('ascii')
|
||||
|
||||
proxy = Proxy.create(
|
||||
proxy_type=self._proxy_type,
|
||||
host=self._proxy_host,
|
||||
port=self._proxy_port,
|
||||
username=self._username,
|
||||
password=self._password,
|
||||
rdns=self._rdns,
|
||||
proxy_ssl=self._proxy_ssl,
|
||||
)
|
||||
|
||||
proxy_stream = proxy.connect(
|
||||
host,
|
||||
port,
|
||||
dest_ssl=ssl_context,
|
||||
timeout=connect_timeout,
|
||||
)
|
||||
|
||||
return SyncStream(sock=proxy_stream.socket)
|
||||
|
||||
def close(self) -> None:
|
||||
if self._connection is not None:
|
||||
self._connection.close()
|
||||
|
||||
def can_handle_request(self, origin: Origin) -> bool:
|
||||
return origin == self._remote_origin
|
||||
|
||||
def is_available(self) -> bool:
|
||||
if self._connection is None: # pragma: no cover
|
||||
# return self._http2 and (self._remote_origin.scheme == b"https" or not self._http1)
|
||||
return False
|
||||
return self._connection.is_available()
|
||||
|
||||
def has_expired(self) -> bool:
|
||||
if self._connection is None:
|
||||
return self._connect_failed
|
||||
return self._connection.has_expired()
|
||||
|
||||
def is_idle(self) -> bool:
|
||||
if self._connection is None:
|
||||
return self._connect_failed
|
||||
return self._connection.is_idle()
|
||||
|
||||
def is_closed(self) -> bool:
|
||||
if self._connection is None:
|
||||
return self._connect_failed
|
||||
return self._connection.is_closed()
|
||||
|
||||
def info(self) -> str: # pragma: no cover
|
||||
if self._connection is None:
|
||||
return "CONNECTION FAILED" if self._connect_failed else "CONNECTING"
|
||||
return self._connection.info()
|
||||
@@ -0,0 +1,40 @@
|
||||
import ssl
|
||||
import typing
|
||||
|
||||
from httpcore._backends.sync import SyncStream as CoreSyncStream
|
||||
from httpcore._utils import is_socket_readable
|
||||
from python_socks.sync.v2._ssl_transport import SSLTransport
|
||||
|
||||
|
||||
class SyncStream(CoreSyncStream):
|
||||
def get_extra_info(self, info: str) -> typing.Any:
|
||||
if info == "ssl_object":
|
||||
if isinstance(self._sock, ssl.SSLSocket):
|
||||
# noinspection PyProtectedMember
|
||||
return self._sock._sslobj # type: ignore
|
||||
if isinstance(self._sock, SSLTransport):
|
||||
return self._sock.sslobj # type: ignore
|
||||
return None
|
||||
|
||||
if info == "client_addr": # pragma: nocover
|
||||
if isinstance(self._sock, SSLTransport):
|
||||
return self._sock.socket.getsockname()
|
||||
else:
|
||||
return self._sock.getsockname()
|
||||
|
||||
if info == "server_addr": # pragma: nocover
|
||||
if isinstance(self._sock, SSLTransport):
|
||||
return self._sock.socket.getpeername()
|
||||
else:
|
||||
return self._sock.getpeername()
|
||||
|
||||
if info == "socket": # pragma: nocover
|
||||
return self._sock # ???
|
||||
|
||||
if info == "is_readable":
|
||||
if isinstance(self._sock, SSLTransport):
|
||||
return is_socket_readable(self._sock.socket)
|
||||
else:
|
||||
return is_socket_readable(self._sock)
|
||||
|
||||
return None # pragma: nocover
|
||||
105
.venv/lib/python3.9/site-packages/httpx_socks/_sync_transport.py
Normal file
105
.venv/lib/python3.9/site-packages/httpx_socks/_sync_transport.py
Normal file
@@ -0,0 +1,105 @@
|
||||
import ssl
|
||||
import typing
|
||||
|
||||
import httpcore
|
||||
|
||||
from httpx import BaseTransport, Request, Response, SyncByteStream, Limits
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
from httpx._config import DEFAULT_LIMITS, create_ssl_context
|
||||
# noinspection PyProtectedMember
|
||||
from httpx._transports.default import ResponseStream, map_httpcore_exceptions
|
||||
|
||||
from ._sync_proxy import SyncProxy
|
||||
from python_socks import ProxyType, parse_proxy_url
|
||||
|
||||
|
||||
class SyncProxyTransport(BaseTransport):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
proxy_type: ProxyType,
|
||||
proxy_host: str,
|
||||
proxy_port: int,
|
||||
username=None,
|
||||
password=None,
|
||||
rdns=None,
|
||||
proxy_ssl: ssl.SSLContext = None,
|
||||
verify=True,
|
||||
cert=None,
|
||||
trust_env: bool = True,
|
||||
limits: Limits = DEFAULT_LIMITS,
|
||||
**kwargs,
|
||||
):
|
||||
ssl_context = create_ssl_context(
|
||||
verify=verify,
|
||||
cert=cert,
|
||||
trust_env=trust_env,
|
||||
http2=kwargs.get('http2', False),
|
||||
)
|
||||
|
||||
self._pool = SyncProxy(
|
||||
proxy_type=proxy_type,
|
||||
proxy_host=proxy_host,
|
||||
proxy_port=proxy_port,
|
||||
username=username,
|
||||
password=password,
|
||||
rdns=rdns,
|
||||
proxy_ssl=proxy_ssl,
|
||||
ssl_context=ssl_context,
|
||||
max_connections=limits.max_connections,
|
||||
max_keepalive_connections=limits.max_keepalive_connections,
|
||||
keepalive_expiry=limits.keepalive_expiry,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def handle_request(self, request: Request) -> Response:
|
||||
assert isinstance(request.stream, SyncByteStream)
|
||||
|
||||
req = httpcore.Request(
|
||||
method=request.method,
|
||||
url=httpcore.URL(
|
||||
scheme=request.url.raw_scheme,
|
||||
host=request.url.raw_host,
|
||||
port=request.url.port,
|
||||
target=request.url.raw_path,
|
||||
),
|
||||
headers=request.headers.raw,
|
||||
content=request.stream,
|
||||
extensions=request.extensions,
|
||||
)
|
||||
|
||||
with map_httpcore_exceptions():
|
||||
resp = self._pool.handle_request(req)
|
||||
|
||||
assert isinstance(resp.stream, typing.Iterable)
|
||||
|
||||
return Response(
|
||||
status_code=resp.status,
|
||||
headers=resp.headers,
|
||||
stream=ResponseStream(resp.stream),
|
||||
extensions=resp.extensions,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_url(cls, url, **kwargs):
|
||||
proxy_type, host, port, username, password = parse_proxy_url(url)
|
||||
return cls(
|
||||
proxy_type=proxy_type,
|
||||
proxy_host=host,
|
||||
proxy_port=port,
|
||||
username=username,
|
||||
password=password,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def close(self) -> None:
|
||||
self._pool.close() # pragma: no cover
|
||||
|
||||
def __enter__(self):
|
||||
self._pool.__enter__()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type=None, exc_value=None, traceback=None):
|
||||
with map_httpcore_exceptions():
|
||||
self._pool.__exit__(exc_type, exc_value, traceback)
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,295 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: python-socks
|
||||
Version: 2.8.1
|
||||
Summary: Proxy (SOCKS4, SOCKS5, HTTP CONNECT) client for Python
|
||||
Author-email: Roman Snegirev <snegiryev@gmail.com>
|
||||
License: Apache-2.0
|
||||
Project-URL: homepage, https://github.com/romis2012/python-socks
|
||||
Project-URL: repository, https://github.com/romis2012/python-socks
|
||||
Keywords: socks,socks5,socks4,http,proxy,asyncio,trio,curio,anyio
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: 3.14
|
||||
Classifier: Operating System :: MacOS
|
||||
Classifier: Operating System :: Microsoft
|
||||
Classifier: Operating System :: POSIX :: Linux
|
||||
Classifier: Topic :: Internet :: WWW/HTTP
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Framework :: AsyncIO
|
||||
Classifier: Framework :: Trio
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Requires-Python: >=3.8.0
|
||||
Description-Content-Type: text/markdown
|
||||
License-File: LICENSE.txt
|
||||
Provides-Extra: asyncio
|
||||
Requires-Dist: async-timeout>=4.0; python_version < "3.11" and extra == "asyncio"
|
||||
Provides-Extra: trio
|
||||
Requires-Dist: trio>=0.24; extra == "trio"
|
||||
Provides-Extra: curio
|
||||
Requires-Dist: curio>=1.4; extra == "curio"
|
||||
Provides-Extra: anyio
|
||||
Requires-Dist: anyio<5.0.0,>=3.3.4; extra == "anyio"
|
||||
Dynamic: license-file
|
||||
|
||||
## python-socks
|
||||
|
||||
[](https://github.com/romis2012/python-socks/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/romis2012/python-socks)
|
||||
[](https://pypi.python.org/pypi/python-socks)
|
||||
[](https://github.com/romis2012/python-socks)
|
||||
<!--
|
||||
[](https://pepy.tech/project/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)
|
||||
```
|
||||
@@ -0,0 +1,149 @@
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_abc.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_connectors/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_connectors/abc.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_connectors/factory_async.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_connectors/factory_sync.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_connectors/http_async.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_connectors/http_sync.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_connectors/socks4_async.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_connectors/socks4_sync.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_connectors/socks5_async.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_connectors/socks5_sync.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_errors.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_helpers.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_protocols/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_protocols/errors.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_protocols/http.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_protocols/socks4.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_protocols/socks5.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_types.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/_version.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/_proxy_chain.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/anyio/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/anyio/_chain.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/anyio/_connect.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/anyio/_proxy.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/anyio/_resolver.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/anyio/_stream.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/anyio/v2/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/anyio/v2/_chain.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/anyio/v2/_connect.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/anyio/v2/_proxy.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/anyio/v2/_stream.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/asyncio/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/asyncio/_connect.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/asyncio/_proxy.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/asyncio/_resolver.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/asyncio/_stream.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/asyncio/v2/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/asyncio/v2/_chain.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/asyncio/v2/_connect.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/asyncio/v2/_proxy.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/asyncio/v2/_stream.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/curio/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/curio/_connect.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/curio/_proxy.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/curio/_resolver.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/curio/_stream.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/trio/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/trio/_connect.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/trio/_proxy.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/trio/_resolver.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/trio/_stream.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/trio/v2/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/trio/v2/_chain.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/trio/v2/_connect.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/trio/v2/_proxy.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/async_/trio/v2/_stream.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/sync/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/sync/_chain.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/sync/_connect.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/sync/_proxy.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/sync/_resolver.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/sync/_stream.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/sync/v2/__init__.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/sync/v2/_chain.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/sync/v2/_connect.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/sync/v2/_proxy.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/sync/v2/_ssl_transport.cpython-39.pyc,,
|
||||
../../../../../../../../Library/Caches/com.apple.python/Users/dannier/Desktop/living/AICLW/wechatAiclaw/.venv/lib/python3.9/site-packages/python_socks/sync/v2/_stream.cpython-39.pyc,,
|
||||
python_socks-2.8.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
python_socks-2.8.1.dist-info/METADATA,sha256=jDS83DnufjrnQhuWQBgKuXA7h0KMUNre3G83vwrOXC4,8151
|
||||
python_socks-2.8.1.dist-info/RECORD,,
|
||||
python_socks-2.8.1.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
||||
python_socks-2.8.1.dist-info/licenses/LICENSE.txt,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
||||
python_socks-2.8.1.dist-info/top_level.txt,sha256=7qyDAVwjTZ0sIYG1iatFbE-SLpWhONjaoMQ0Ib2DX0Q,13
|
||||
python_socks/__init__.py,sha256=gKywvwx_x4_0hWJEa2eif1dPzP9YeFXJdrNlHuvFmC8,367
|
||||
python_socks/_abc.py,sha256=_juTZE0iqoTChE53IHjBKEYFO-fxYZfPhdUc6EgFlwE,909
|
||||
python_socks/_connectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
python_socks/_connectors/abc.py,sha256=1fgjPiv64UAMiJDsJXYr-GofPGhuHTwTzaodQfrzf7Q,397
|
||||
python_socks/_connectors/factory_async.py,sha256=X63hJuhpr8iDdAxwrUlsbyXfHewZJtgKQRdSZs41RS4,1055
|
||||
python_socks/_connectors/factory_sync.py,sha256=QVaNbUXSEPZW9Cv43gRFF3kwVwU64moQR1BEtE4VOiE,1042
|
||||
python_socks/_connectors/http_async.py,sha256=vo8QhluTYEqnIoxL2XPcj9vSl8yhu9Tvm4KI-gMyJGM,951
|
||||
python_socks/_connectors/http_sync.py,sha256=Q6DRrUCljokQe2veoJJ6G8SZ7QEmq8x4_Q-faSADLEY,926
|
||||
python_socks/_connectors/socks4_async.py,sha256=QGoB1are0qU-uoPCUvanFBUvSCyFxgKN_XVMKSrDoYY,1179
|
||||
python_socks/_connectors/socks4_sync.py,sha256=yX4e3xMOHH1OzVPTYROeQDBf8gcZcXODFOxBHyRtVOc,1148
|
||||
python_socks/_connectors/socks5_async.py,sha256=qniozb0P8gtXF_vK3b7kjRmoGNMHmYAb9eIZg-heI8k,2829
|
||||
python_socks/_connectors/socks5_sync.py,sha256=G_FW1XsiaMDbzR7vcBMtRpD3R9LpO5V5zu9pltpo7xI,2602
|
||||
python_socks/_errors.py,sha256=M2KkgCrFy_ebEqw7JoxCWMjIIQ5Zu_B7YlXYhsLo-gk,329
|
||||
python_socks/_helpers.py,sha256=UacO07M9i8G8CfqzBYWnehgCTFbZTlwJWAsFoXR3mVk,2708
|
||||
python_socks/_protocols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
python_socks/_protocols/errors.py,sha256=4XqU4AqgrWaedmUVGMjd6TvQKXv3mV0H76s5lf8aazY,150
|
||||
python_socks/_protocols/http.py,sha256=QhLHqBgc6DfsvC8AIhylN1tjfk7QzxgwemrK1LuL3vk,4499
|
||||
python_socks/_protocols/socks4.py,sha256=j1Psqi3PDiKAF19g_Mh3sf_RwXu7o-8X7BqWOaoTbKE,3061
|
||||
python_socks/_protocols/socks5.py,sha256=1PEr1s4JxmpST9OYEeVgbidnV_d4Vb85JZHmWqK-r9c,9713
|
||||
python_socks/_types.py,sha256=dgQifNf2mIwdKoX7YmI91H_lUsW9-Y0ZkZGlIV6_NNw,90
|
||||
python_socks/_version.py,sha256=e7j6da3GYTfq-hRQ9-hzCSija3gACzgMjjpG6A56uGs,49
|
||||
python_socks/async_/__init__.py,sha256=TeBn5oirLy6aPoWcTc3OW9OxXh5Uv5LADwF_Y7f7HjY,64
|
||||
python_socks/async_/_proxy_chain.py,sha256=TiG1Uu45anONWi2ZFj11w4-LrNbja8SbG27bWCX9Et4,987
|
||||
python_socks/async_/anyio/__init__.py,sha256=pZnrND4Qd-pIGg7KI1ML41kZNkWHmkd3vrvwZZIJyk4,106
|
||||
python_socks/async_/anyio/_chain.py,sha256=-d4sR-JM8Agh73rB8nK7DBcB9oqDgtx2GOTAx-SMslU,1099
|
||||
python_socks/async_/anyio/_connect.py,sha256=dYx-X2rDDTe-mtLm6DTpV-gFB7Vt_OYS1Xs_4Ea9JZ4,306
|
||||
python_socks/async_/anyio/_proxy.py,sha256=nWHzIFPYPXatmuVITdDWN6uoQe_P2Xm6NomgfgYm5UA,4322
|
||||
python_socks/async_/anyio/_resolver.py,sha256=LPRLmEzgKPGCqAbEMmLSMVtFkNVKha_2u08MhrZZ-CY,590
|
||||
python_socks/async_/anyio/_stream.py,sha256=OcCl4YkccA0-5hZh90CP4YmsBDLPwHpS3B0wCLytnAY,1614
|
||||
python_socks/async_/anyio/v2/__init__.py,sha256=4xgyAJHDGpRANYVNfpUdn3Zo8Ib_gREWH8bH4APycK4,117
|
||||
python_socks/async_/anyio/v2/_chain.py,sha256=NZ_9dA1AQrG3pBOnYBszbtD-Kse85bQ_dT33VtmGx3o,797
|
||||
python_socks/async_/anyio/v2/_connect.py,sha256=_LF1f_cYJbfdSqkq-xEK4nF4l-bP5I1VWRFFyX1RH3Y,368
|
||||
python_socks/async_/anyio/v2/_proxy.py,sha256=7F-Y_xhVcqGnKjUDVnjYNkyz5VEFJjzZhkl0Xklyj9U,4085
|
||||
python_socks/async_/anyio/v2/_stream.py,sha256=40D3Pmb0rryk9gaQlmjpAMdiJi5H5O0xbBseblGG1FY,1616
|
||||
python_socks/async_/asyncio/__init__.py,sha256=0WAM07vy0YvI8IhKGmaUA0GaNLHaG3l-4mnJVbfoqHE,65
|
||||
python_socks/async_/asyncio/_connect.py,sha256=K4qqOwXI-1uVleSwf-mQ2ZPO5LwKeSr50Wr3jWImEng,1110
|
||||
python_socks/async_/asyncio/_proxy.py,sha256=HS1NfXZCYOZ2b4qUee01CwH4NF1OcXPzqYxEJXYJeT8,4120
|
||||
python_socks/async_/asyncio/_resolver.py,sha256=ROqOncuIub87J9_nMdvRTG_5zYOh8uafk265z0s2ORQ,681
|
||||
python_socks/async_/asyncio/_stream.py,sha256=RP7BIP9iFFy5nLRf9kHH7s1h_zPfo0yoEVKNpExqFcI,1021
|
||||
python_socks/async_/asyncio/v2/__init__.py,sha256=v9eJQeZ2yGJHeBQ0FYqNmIMCRrB547QqoAANHVBitxY,108
|
||||
python_socks/async_/asyncio/v2/_chain.py,sha256=KCtmKg0halaCIEqBY6JB-KGVNNiaa4APBb5l5z8kVPk,811
|
||||
python_socks/async_/asyncio/v2/_connect.py,sha256=3yPmPCNmMKlCCZZCnNvydER3ilnfE5gTevzBN7IoGEE,603
|
||||
python_socks/async_/asyncio/v2/_proxy.py,sha256=QPMx2nFeHCKObBkDqzONkgf2VLXdhuP-aMsBWAgdf-I,4714
|
||||
python_socks/async_/asyncio/v2/_stream.py,sha256=YdpJap_Ax-7tJnA-AJ7nayZmD5lpem2UIPbUepnzqxM,2753
|
||||
python_socks/async_/curio/__init__.py,sha256=1OyhLhvLJ23iDROgyxGqQACice66SrIYCMqmwjw_dqY,63
|
||||
python_socks/async_/curio/_connect.py,sha256=u1HRJ8n3OZbux-wETO9HuqZv9By2LGhhSsM78gnxr_0,328
|
||||
python_socks/async_/curio/_proxy.py,sha256=_Qgodw62VPvt-5j1T34l7P52R5Bk4Crq97QJ0f1Qf10,3644
|
||||
python_socks/async_/curio/_resolver.py,sha256=_jDZq_lzTl_QihxH2HNEY_7_ELo-qmzG6IO5PNNEOek,722
|
||||
python_socks/async_/curio/_stream.py,sha256=sIAbwkPuiFF4c8cEs7exVdGb7cfFgoggKTV4LYqDcm0,855
|
||||
python_socks/async_/trio/__init__.py,sha256=Y68xwGqkXGKXHcwM6nvev6n2ChbFpBUFW0RzBrsIrug,61
|
||||
python_socks/async_/trio/_connect.py,sha256=qVfK4jz3h1Tjwnr8sX0DNazASdg3DDXNHjMopaHGY0I,851
|
||||
python_socks/async_/trio/_proxy.py,sha256=yp3NJOTAaw2ReXlPt4fBRY51Xqc1C3tf16H3v0XvlCg,3786
|
||||
python_socks/async_/trio/_resolver.py,sha256=uqBoTkbYpwwESoph2SmXRUUHM_DsT7dALQDmKqp-sco,591
|
||||
python_socks/async_/trio/_stream.py,sha256=xunx_XUiwH1pBLQjWbkqq3P92SeXaRypPMPRYwHOsPg,1003
|
||||
python_socks/async_/trio/v2/__init__.py,sha256=cd8Sc6NLmrUZewkqeDvnxmQqGZBKNuwYEEb4NMW2qo0,116
|
||||
python_socks/async_/trio/v2/_chain.py,sha256=g9atl9HcWYrCHLCI8GtLR9GZC67sKiXEjcvGwZVGhDw,795
|
||||
python_socks/async_/trio/v2/_connect.py,sha256=Nk0ipvuFFnzH-NPfXkX57I74-sbT6ks--ZgbfN3r42o,360
|
||||
python_socks/async_/trio/v2/_proxy.py,sha256=9118wyv9SthZNVlvECfb3OfhrPMOFyyJSHhl7lG0TbI,4094
|
||||
python_socks/async_/trio/v2/_stream.py,sha256=wGB7sDjRO1ywVNjF_DFgdF2orjD3zrh8dPSmW9aBEV0,1475
|
||||
python_socks/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
python_socks/sync/__init__.py,sha256=Dslh61MLGktjBK50UVGYjAQ-iGl8tjoLR93_-cvddWM,106
|
||||
python_socks/sync/_chain.py,sha256=do_WtGenqKAG7B50nNZdltFqEQV8t-mSlGnMACNIwgE,973
|
||||
python_socks/sync/_connect.py,sha256=N1Y-uQXkOiJ_dxkwWHw1fBliIQlTL47ydql7IvaSqm8,344
|
||||
python_socks/sync/_proxy.py,sha256=Ny2QA8GI2aCUm0JL8IfY2j2IaXKzAZNp6bZeF7OmwYA,3287
|
||||
python_socks/sync/_resolver.py,sha256=2rf9-NdjRktYrab1Oyv9ZJLKByP95ysFmRE3TgjRCSA,547
|
||||
python_socks/sync/_stream.py,sha256=VeR-_ERvn5x_PjxFsPy88llEiNfTuejvXoZkAxlqOuY,811
|
||||
python_socks/sync/v2/__init__.py,sha256=eVZc6zh0pLhNMf_uSPZiH_1vNGEvedwzX5gObUSkRT8,116
|
||||
python_socks/sync/v2/_chain.py,sha256=IRVMioTVWwxylZ8maD5Gqxhhgw4VyrG5vo_hAOuEn8Q,579
|
||||
python_socks/sync/v2/_connect.py,sha256=QrZ1CZimYCUMUuaD8JdUblwlqlzIbqFrbsPwMzoxGkM,420
|
||||
python_socks/sync/v2/_proxy.py,sha256=7FuO6wML0DkEonoGAprhTlVd8fazOgNeNrJyxABmP1Q,3621
|
||||
python_socks/sync/v2/_ssl_transport.py,sha256=MURwzvKu-S2-MI7o8KaZTvvBFOiiKIu5R0ERVIMvERE,6088
|
||||
python_socks/sync/v2/_stream.py,sha256=Jq89x1xmIX3L_v-WyS1nRTItjTnYTcDcfDblsANxfJI,1544
|
||||
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (82.0.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
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.
|
||||
@@ -0,0 +1 @@
|
||||
python_socks
|
||||
20
.venv/lib/python3.9/site-packages/python_socks/__init__.py
Normal file
20
.venv/lib/python3.9/site-packages/python_socks/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from ._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',
|
||||
)
|
||||
40
.venv/lib/python3.9/site-packages/python_socks/_abc.py
Normal file
40
.venv/lib/python3.9/site-packages/python_socks/_abc.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from 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()
|
||||
@@ -0,0 +1,21 @@
|
||||
from .._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
|
||||
@@ -0,0 +1,40 @@
|
||||
from 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}')
|
||||
@@ -0,0 +1,40 @@
|
||||
from 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}')
|
||||
@@ -0,0 +1,38 @@
|
||||
from 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
|
||||
@@ -0,0 +1,38 @@
|
||||
from 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
|
||||
@@ -0,0 +1,45 @@
|
||||
import 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
|
||||
@@ -0,0 +1,45 @@
|
||||
import 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
|
||||
@@ -0,0 +1,95 @@
|
||||
import 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(3)
|
||||
if data[0] != socks5.SOCKS_VER:
|
||||
return data
|
||||
if data[1] != socks5.ReplyCode.SUCCEEDED:
|
||||
return data
|
||||
if data[2] != socks5.RSV:
|
||||
return data
|
||||
|
||||
data += await stream.read_exact(1)
|
||||
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
|
||||
@@ -0,0 +1,86 @@
|
||||
import 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(3)
|
||||
if data[0] != socks5.SOCKS_VER:
|
||||
return data
|
||||
if data[1] != socks5.ReplyCode.SUCCEEDED:
|
||||
return data
|
||||
if data[2] != socks5.RSV:
|
||||
return data
|
||||
|
||||
data += stream.read_exact(1)
|
||||
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
|
||||
16
.venv/lib/python3.9/site-packages/python_socks/_errors.py
Normal file
16
.venv/lib/python3.9/site-packages/python_socks/_errors.py
Normal file
@@ -0,0 +1,16 @@
|
||||
class ProxyException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProxyTimeoutError(ProxyException, TimeoutError):
|
||||
pass
|
||||
|
||||
|
||||
class ProxyConnectionError(ProxyException, OSError):
|
||||
pass
|
||||
|
||||
|
||||
class ProxyError(ProxyException):
|
||||
def __init__(self, message, error_code=None):
|
||||
super().__init__(message)
|
||||
self.error_code = error_code
|
||||
81
.venv/lib/python3.9/site-packages/python_socks/_helpers.py
Normal file
81
.venv/lib/python3.9/site-packages/python_socks/_helpers.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import 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
|
||||
@@ -0,0 +1,4 @@
|
||||
class ReplyError(Exception):
|
||||
def __init__(self, message, error_code=None):
|
||||
super().__init__(message)
|
||||
self.error_code = error_code
|
||||
@@ -0,0 +1,148 @@
|
||||
import 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)
|
||||
@@ -0,0 +1,116 @@
|
||||
import 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)
|
||||
@@ -0,0 +1,355 @@
|
||||
import 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
|
||||
7
.venv/lib/python3.9/site-packages/python_socks/_types.py
Normal file
7
.venv/lib/python3.9/site-packages/python_socks/_types.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ProxyType(Enum):
|
||||
SOCKS4 = 1
|
||||
SOCKS5 = 2
|
||||
HTTP = 3
|
||||
@@ -0,0 +1,2 @@
|
||||
__title__ = 'python-socks'
|
||||
__version__ = '2.8.1'
|
||||
@@ -0,0 +1,3 @@
|
||||
from ._proxy_chain import ProxyChain
|
||||
|
||||
__all__ = ('ProxyChain',)
|
||||
@@ -0,0 +1,34 @@
|
||||
from 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
|
||||
@@ -0,0 +1,4 @@
|
||||
from ._proxy import AnyioProxy as Proxy
|
||||
from ._chain import ProxyChain
|
||||
|
||||
__all__ = ('Proxy', 'ProxyChain')
|
||||
@@ -0,0 +1,42 @@
|
||||
from 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
|
||||
@@ -0,0 +1,16 @@
|
||||
from 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,
|
||||
)
|
||||
@@ -0,0 +1,137 @@
|
||||
import 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)
|
||||
@@ -0,0 +1,22 @@
|
||||
import 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]
|
||||
@@ -0,0 +1,59 @@
|
||||
import 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
|
||||
@@ -0,0 +1,7 @@
|
||||
from ._proxy import AnyioProxy as Proxy
|
||||
from ._chain import ProxyChain
|
||||
|
||||
__all__ = (
|
||||
'Proxy',
|
||||
'ProxyChain',
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
from 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,
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
from 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)
|
||||
@@ -0,0 +1,135 @@
|
||||
import 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)
|
||||
@@ -0,0 +1,59 @@
|
||||
import 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
|
||||
@@ -0,0 +1,4 @@
|
||||
from ._proxy import AsyncioProxy as Proxy
|
||||
|
||||
|
||||
__all__ = ('Proxy',)
|
||||
@@ -0,0 +1,43 @@
|
||||
import 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)
|
||||
@@ -0,0 +1,143 @@
|
||||
import 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)
|
||||
@@ -0,0 +1,25 @@
|
||||
import 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]
|
||||
@@ -0,0 +1,36 @@
|
||||
import 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()
|
||||
@@ -0,0 +1,4 @@
|
||||
from ._proxy import AsyncioProxy as Proxy
|
||||
from ._chain import ProxyChain
|
||||
|
||||
__all__ = ('Proxy', 'ProxyChain')
|
||||
@@ -0,0 +1,32 @@
|
||||
from 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,
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
import 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,
|
||||
)
|
||||
@@ -0,0 +1,157 @@
|
||||
import 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)
|
||||
@@ -0,0 +1,91 @@
|
||||
import 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
|
||||
@@ -0,0 +1,4 @@
|
||||
from ._proxy import CurioProxy as Proxy
|
||||
|
||||
|
||||
__all__ = ('Proxy',)
|
||||
@@ -0,0 +1,17 @@
|
||||
from 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,
|
||||
)
|
||||
@@ -0,0 +1,132 @@
|
||||
from 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)
|
||||
@@ -0,0 +1,25 @@
|
||||
import 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]
|
||||
@@ -0,0 +1,32 @@
|
||||
import 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()
|
||||
@@ -0,0 +1,3 @@
|
||||
from ._proxy import TrioProxy as Proxy
|
||||
|
||||
__all__ = ('Proxy',)
|
||||
@@ -0,0 +1,36 @@
|
||||
from 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)
|
||||
|
||||
try:
|
||||
await sock.connect((host, port))
|
||||
except OSError:
|
||||
sock.close()
|
||||
raise
|
||||
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)
|
||||
@@ -0,0 +1,131 @@
|
||||
from 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)
|
||||
@@ -0,0 +1,21 @@
|
||||
import 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]
|
||||
@@ -0,0 +1,35 @@
|
||||
import 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()
|
||||
@@ -0,0 +1,7 @@
|
||||
from ._proxy import TrioProxy as Proxy
|
||||
from ._chain import ProxyChain
|
||||
|
||||
__all__ = (
|
||||
'Proxy',
|
||||
'ProxyChain',
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
from 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,
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
from 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)
|
||||
@@ -0,0 +1,135 @@
|
||||
import 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)
|
||||
@@ -0,0 +1,55 @@
|
||||
import 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
|
||||
@@ -0,0 +1,5 @@
|
||||
from ._proxy import SyncProxy as Proxy
|
||||
from ._chain import ProxyChain
|
||||
|
||||
|
||||
__all__ = ('Proxy', 'ProxyChain')
|
||||
@@ -0,0 +1,32 @@
|
||||
from 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
|
||||
@@ -0,0 +1,16 @@
|
||||
import 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,
|
||||
)
|
||||
116
.venv/lib/python3.9/site-packages/python_socks/sync/_proxy.py
Normal file
116
.venv/lib/python3.9/site-packages/python_socks/sync/_proxy.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import 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)
|
||||
@@ -0,0 +1,16 @@
|
||||
import 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]
|
||||
@@ -0,0 +1,32 @@
|
||||
import socket
|
||||
|
||||
from .._errors import ProxyError
|
||||
from .. import _abc as abc
|
||||
|
||||
DEFAULT_RECEIVE_SIZE = 65536
|
||||
|
||||
|
||||
class SyncSocketStream(abc.SyncSocketStream):
|
||||
_socket: socket.socket
|
||||
|
||||
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()
|
||||
@@ -0,0 +1,7 @@
|
||||
from ._proxy import SyncProxy as Proxy
|
||||
from ._chain import ProxyChain
|
||||
|
||||
__all__ = (
|
||||
'Proxy',
|
||||
'ProxyChain',
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
from 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,
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
import 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)
|
||||
121
.venv/lib/python3.9/site-packages/python_socks/sync/v2/_proxy.py
Normal file
121
.venv/lib/python3.9/site-packages/python_socks/sync/v2/_proxy.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import 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)
|
||||
@@ -0,0 +1,200 @@
|
||||
"""
|
||||
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
|
||||
@@ -0,0 +1,56 @@
|
||||
import 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
|
||||
11
Dockerfile
11
Dockerfile
@@ -17,11 +17,14 @@ WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1
|
||||
PYTHONUNBUFFERED=1 \
|
||||
TZ=Asia/Shanghai
|
||||
|
||||
# Alpine 使用国内镜像,加速 apk 安装(python3/py3-pip)
|
||||
# Alpine 使用国内镜像,加速 apk 安装(python3/py3-pip)并设置时区为 Asia/Shanghai(与宿主机保持一致)
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||
&& apk add --no-cache python3 py3-pip
|
||||
&& apk add --no-cache python3 py3-pip tzdata \
|
||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||
&& echo "Asia/Shanghai" > /etc/timezone
|
||||
|
||||
COPY --from=build /app/node_modules ./node_modules
|
||||
COPY --from=build /app/dist ./dist
|
||||
@@ -49,7 +52,9 @@ RUN pip3 install --no-cache-dir --break-system-packages \
|
||||
python3 -c "import fastapi; import uvicorn; import httpx; import websockets; import openai; print('all deps ok')"
|
||||
|
||||
COPY backend ./backend
|
||||
# 仅复制模板,不复制 .env / .env.prod(由 run-docker.sh --env-file .env.prod 注入)
|
||||
COPY .env.example ./
|
||||
COPY .env.prod.example ./
|
||||
COPY start.sh ./
|
||||
|
||||
EXPOSE 3000 8000
|
||||
|
||||
@@ -3,11 +3,16 @@ FROM docker.m.daocloud.io/library/python:3.11-slim
|
||||
WORKDIR /app
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1
|
||||
PYTHONUNBUFFERED=1 \
|
||||
TZ=Asia/Shanghai
|
||||
|
||||
COPY backend/requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends tzdata \
|
||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||
&& echo "Asia/Shanghai" > /etc/timezone \
|
||||
&& pip install --no-cache-dir -r requirements.txt \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY backend ./backend
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user