From dd808b1d689c323e37505f7b6600c53c08a1a9e1 Mon Sep 17 00:00:00 2001 From: Matthias Guillitte Date: Mon, 20 Jul 2026 14:21:41 +0200 Subject: [PATCH] Ignore venv --- .gitignore | 1 + .../urllib3-2.7.0.dist-info/INSTALLER | 1 - .../urllib3-2.7.0.dist-info/METADATA | 163 -- .../urllib3-2.7.0.dist-info/RECORD | 79 - .../urllib3-2.7.0.dist-info/WHEEL | 4 - .../licenses/LICENSE.txt | 21 - .../site-packages/urllib3/__init__.py | 155 +- .../__pycache__/__init__.cpython-312.pyc | Bin 7319 -> 3409 bytes .../_base_connection.cpython-312.pyc | Bin 6869 -> 0 bytes .../__pycache__/_collections.cpython-312.pyc | Bin 22848 -> 16492 bytes .../_request_methods.cpython-312.pyc | Bin 10611 -> 0 bytes .../__pycache__/_version.cpython-312.pyc | Bin 648 -> 222 bytes .../__pycache__/connection.cpython-312.pyc | Bin 38920 -> 20411 bytes .../connectionpool.cpython-312.pyc | Bin 40113 -> 36547 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 16613 -> 13497 bytes .../__pycache__/fields.cpython-312.pyc | Bin 12020 -> 10417 bytes .../__pycache__/filepost.cpython-312.pyc | Bin 3498 -> 4022 bytes .../__pycache__/poolmanager.cpython-312.pyc | Bin 24433 -> 20476 bytes .../__pycache__/response.cpython-312.pyc | Bin 57709 -> 34152 bytes .../site-packages/urllib3/_base_connection.py | 167 --- .../site-packages/urllib3/_collections.py | 409 ++--- .../site-packages/urllib3/_request_methods.py | 278 ---- .../site-packages/urllib3/_version.py | 26 +- .../site-packages/urllib3/connection.py | 1159 ++++---------- .../site-packages/urllib3/connectionpool.py | 997 ++++++------- .../__pycache__/__init__.cpython-312.pyc | Bin 202 -> 202 bytes .../__pycache__/pyopenssl.cpython-312.pyc | Bin 28224 -> 24421 bytes .../contrib/__pycache__/socks.cpython-312.pyc | Bin 8180 -> 7515 bytes .../urllib3/contrib/emscripten/__init__.py | 17 - .../__pycache__/__init__.cpython-312.pyc | Bin 999 -> 0 bytes .../__pycache__/connection.cpython-312.pyc | Bin 10396 -> 0 bytes .../__pycache__/fetch.cpython-312.pyc | Bin 28647 -> 0 bytes .../__pycache__/request.cpython-312.pyc | Bin 1430 -> 0 bytes .../__pycache__/response.cpython-312.pyc | Bin 12354 -> 0 bytes .../urllib3/contrib/emscripten/connection.py | 260 ---- .../emscripten/emscripten_fetch_worker.js | 110 -- .../urllib3/contrib/emscripten/fetch.py | 726 --------- .../urllib3/contrib/emscripten/request.py | 22 - .../urllib3/contrib/emscripten/response.py | 281 ---- .../urllib3/contrib/pyopenssl.py | 413 +++-- .../site-packages/urllib3/contrib/socks.py | 88 +- .../site-packages/urllib3/exceptions.py | 208 ++- .../site-packages/urllib3/fields.py | 259 ++-- .../site-packages/urllib3/filepost.py | 65 +- .../site-packages/urllib3/http2/__init__.py | 53 - .../__pycache__/__init__.cpython-312.pyc | Bin 1755 -> 0 bytes .../__pycache__/connection.cpython-312.pyc | Bin 17062 -> 0 bytes .../http2/__pycache__/probe.cpython-312.pyc | Bin 3706 -> 0 bytes .../site-packages/urllib3/http2/connection.py | 356 ----- .../site-packages/urllib3/http2/probe.py | 87 -- .../site-packages/urllib3/poolmanager.py | 365 ++--- .../python3.12/site-packages/urllib3/py.typed | 2 - .../site-packages/urllib3/response.py | 1328 +++++------------ .../site-packages/urllib3/util/__init__.py | 19 +- .../util/__pycache__/__init__.cpython-312.pyc | Bin 1015 -> 1150 bytes .../__pycache__/connection.cpython-312.pyc | Bin 4705 -> 4760 bytes .../util/__pycache__/proxy.cpython-312.pyc | Bin 1227 -> 1556 bytes .../util/__pycache__/request.cpython-312.pyc | Bin 8351 -> 4426 bytes .../util/__pycache__/response.cpython-312.pyc | Bin 2883 -> 2993 bytes .../util/__pycache__/retry.cpython-312.pyc | Bin 21185 -> 21722 bytes .../util/__pycache__/ssl_.cpython-312.pyc | Bin 15682 -> 15369 bytes .../ssl_match_hostname.cpython-312.pyc | Bin 5424 -> 5075 bytes .../__pycache__/ssltransport.cpython-312.pyc | Bin 13334 -> 10776 bytes .../util/__pycache__/timeout.cpython-312.pyc | Bin 11699 -> 11143 bytes .../util/__pycache__/url.cpython-312.pyc | Bin 16236 -> 15775 bytes .../util/__pycache__/util.cpython-312.pyc | Bin 2004 -> 0 bytes .../util/__pycache__/wait.cpython-312.pyc | Bin 3450 -> 4407 bytes .../site-packages/urllib3/util/connection.py | 80 +- .../site-packages/urllib3/util/proxy.py | 38 +- .../site-packages/urllib3/util/request.py | 177 +-- .../site-packages/urllib3/util/response.py | 78 +- .../site-packages/urllib3/util/retry.py | 383 +++-- .../site-packages/urllib3/util/ssl_.py | 533 +++---- .../urllib3/util/ssl_match_hostname.py | 92 +- .../urllib3/util/ssltransport.py | 160 +- .../site-packages/urllib3/util/timeout.py | 116 +- .../site-packages/urllib3/util/url.py | 394 +++-- .../site-packages/urllib3/util/util.py | 42 - .../site-packages/urllib3/util/wait.py | 88 +- 79 files changed, 2952 insertions(+), 7348 deletions(-) delete mode 100644 venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/INSTALLER delete mode 100644 venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/METADATA delete mode 100644 venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/RECORD delete mode 100644 venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/WHEEL delete mode 100644 venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/licenses/LICENSE.txt delete mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/_base_connection.cpython-312.pyc delete mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/_request_methods.cpython-312.pyc delete mode 100644 venv/lib/python3.12/site-packages/urllib3/_base_connection.py delete mode 100644 venv/lib/python3.12/site-packages/urllib3/_request_methods.py delete mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__init__.py delete mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/__init__.cpython-312.pyc delete mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/connection.cpython-312.pyc delete mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/fetch.cpython-312.pyc delete mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/request.cpython-312.pyc delete mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/response.cpython-312.pyc delete mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/connection.py delete mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js delete mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/fetch.py delete mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/request.py delete mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/response.py delete mode 100644 venv/lib/python3.12/site-packages/urllib3/http2/__init__.py delete mode 100644 venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/__init__.cpython-312.pyc delete mode 100644 venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/connection.cpython-312.pyc delete mode 100644 venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/probe.cpython-312.pyc delete mode 100644 venv/lib/python3.12/site-packages/urllib3/http2/connection.py delete mode 100644 venv/lib/python3.12/site-packages/urllib3/http2/probe.py delete mode 100644 venv/lib/python3.12/site-packages/urllib3/py.typed delete mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/util.cpython-312.pyc delete mode 100644 venv/lib/python3.12/site-packages/urllib3/util/util.py diff --git a/.gitignore b/.gitignore index 8c34a8b..4bd39c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__/ orchestrateur/db.sqlite-shm +venv/ \ No newline at end of file diff --git a/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/METADATA b/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/METADATA deleted file mode 100644 index 9c7a470..0000000 --- a/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/METADATA +++ /dev/null @@ -1,163 +0,0 @@ -Metadata-Version: 2.4 -Name: urllib3 -Version: 2.7.0 -Summary: HTTP library with thread-safe connection pooling, file post, and more. -Project-URL: Changelog, https://github.com/urllib3/urllib3/blob/main/CHANGES.rst -Project-URL: Documentation, https://urllib3.readthedocs.io -Project-URL: Code, https://github.com/urllib3/urllib3 -Project-URL: Issue tracker, https://github.com/urllib3/urllib3/issues -Author-email: Andrey Petrov -Maintainer-email: Seth Michael Larson , Quentin Pradet , Illia Volochii -License-Expression: MIT -License-File: LICENSE.txt -Keywords: filepost,http,httplib,https,pooling,ssl,threadsafe,urllib -Classifier: Environment :: Web Environment -Classifier: Intended Audience :: Developers -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3 :: Only -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: Programming Language :: Python :: Free Threading :: 2 - Beta -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Internet :: WWW/HTTP -Classifier: Topic :: Software Development :: Libraries -Requires-Python: >=3.10 -Provides-Extra: brotli -Requires-Dist: brotli>=1.2.0; (platform_python_implementation == 'CPython') and extra == 'brotli' -Requires-Dist: brotlicffi>=1.2.0.0; (platform_python_implementation != 'CPython') and extra == 'brotli' -Provides-Extra: h2 -Requires-Dist: h2<5,>=4; extra == 'h2' -Provides-Extra: socks -Requires-Dist: pysocks!=1.5.7,<2.0,>=1.5.6; extra == 'socks' -Provides-Extra: zstd -Requires-Dist: backports-zstd>=1.0.0; (python_version < '3.14') and extra == 'zstd' -Description-Content-Type: text/markdown - -

- -![urllib3](https://github.com/urllib3/urllib3/raw/main/docs/_static/banner_github.svg) - -

- -

- PyPI Version - Python Versions - Join our Discord - Coverage Status - Build Status on GitHub - Documentation Status
- OpenSSF Scorecard - SLSA 3 - CII Best Practices -

- -urllib3 is a powerful, *user-friendly* HTTP client for Python. -urllib3 brings many critical features that are missing from the Python -standard libraries: - -- Thread safety. -- Connection pooling. -- Client-side SSL/TLS verification. -- File uploads with multipart encoding. -- Helpers for retrying requests and dealing with HTTP redirects. -- Support for gzip, deflate, brotli, and zstd encoding. -- Proxy support for HTTP and SOCKS. -- 100% test coverage. - -... and many more features, but most importantly: Our maintainers have a 15+ -year track record of maintaining urllib3 with the highest code standards and -attention to security and safety. - -[Much of the Python ecosystem already uses urllib3](https://urllib3.readthedocs.io/en/stable/#who-uses) -and you should too. - - -## Installing - -urllib3 can be installed with [pip](https://pip.pypa.io): - -```bash -$ python -m pip install urllib3 -``` - -Alternatively, you can grab the latest source code from [GitHub](https://github.com/urllib3/urllib3): - -```bash -$ git clone https://github.com/urllib3/urllib3.git -$ cd urllib3 -$ pip install . -``` - -## Getting Started - -urllib3 is easy to use: - -```python3 ->>> import urllib3 ->>> resp = urllib3.request("GET", "http://httpbin.org/robots.txt") ->>> resp.status -200 ->>> resp.data -b"User-agent: *\nDisallow: /deny\n" -``` - -urllib3 has usage and reference documentation at [urllib3.readthedocs.io](https://urllib3.readthedocs.io). - - -## Community - -urllib3 has a [community Discord channel](https://discord.gg/urllib3) for asking questions and -collaborating with other contributors. Drop by and say hello 👋 - - -## Contributing - -urllib3 happily accepts contributions. Please see our -[contributing documentation](https://urllib3.readthedocs.io/en/latest/contributing.html) -for some tips on getting started. - - -## Security Disclosures - -To report a security vulnerability, please use the -[Tidelift security contact](https://tidelift.com/security). -Tidelift will coordinate the fix and disclosure with maintainers. - - -## Maintainers - -Meet our maintainers since 2008: - -- Current Lead: [@illia-v](https://github.com/illia-v) (Illia Volochii) -- [@sethmlarson](https://github.com/sethmlarson) (Seth M. Larson) -- [@pquentin](https://github.com/pquentin) (Quentin Pradet) -- [@theacodes](https://github.com/theacodes) (Thea Flowers) -- [@haikuginger](https://github.com/haikuginger) (Jess Shapiro) -- [@lukasa](https://github.com/lukasa) (Cory Benfield) -- [@sigmavirus24](https://github.com/sigmavirus24) (Ian Stapleton Cordasco) -- [@shazow](https://github.com/shazow) (Andrey Petrov) - -👋 - - -## Sponsorship - -If your company benefits from this library, please consider [sponsoring its -development](https://urllib3.readthedocs.io/en/latest/sponsors.html). - - -## For Enterprise - -Professional support for urllib3 is available as part of the [Tidelift -Subscription][1]. Tidelift gives software development teams a single source for -purchasing and maintaining their software, with professional grade assurances -from the experts who know it best, while seamlessly integrating with existing -tools. - -[1]: https://tidelift.com/subscription/pkg/pypi-urllib3?utm_source=pypi-urllib3&utm_medium=referral&utm_campaign=readme diff --git a/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/RECORD b/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/RECORD deleted file mode 100644 index 666626e..0000000 --- a/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/RECORD +++ /dev/null @@ -1,79 +0,0 @@ -urllib3-2.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -urllib3-2.7.0.dist-info/METADATA,sha256=ZhR4VtMLu2vtAcbOMybQ9I2E7V8oasF3YzoUhrgurOU,6852 -urllib3-2.7.0.dist-info/RECORD,, -urllib3-2.7.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87 -urllib3-2.7.0.dist-info/licenses/LICENSE.txt,sha256=Ew46ZNX91dCWp1JpRjSn2d8oRGnehuVzIQAmgEHj1oY,1093 -urllib3/__init__.py,sha256=JMo1tg1nIV1AeJ2vENC_Txfl0e5h6Gzl9DGVk1rWRbo,6979 -urllib3/__pycache__/__init__.cpython-312.pyc,, -urllib3/__pycache__/_base_connection.cpython-312.pyc,, -urllib3/__pycache__/_collections.cpython-312.pyc,, -urllib3/__pycache__/_request_methods.cpython-312.pyc,, -urllib3/__pycache__/_version.cpython-312.pyc,, -urllib3/__pycache__/connection.cpython-312.pyc,, -urllib3/__pycache__/connectionpool.cpython-312.pyc,, -urllib3/__pycache__/exceptions.cpython-312.pyc,, -urllib3/__pycache__/fields.cpython-312.pyc,, -urllib3/__pycache__/filepost.cpython-312.pyc,, -urllib3/__pycache__/poolmanager.cpython-312.pyc,, -urllib3/__pycache__/response.cpython-312.pyc,, -urllib3/_base_connection.py,sha256=HzcSEHexgDrRUr60jNniB2KQjdw97SedD5luRfHtCXg,5580 -urllib3/_collections.py,sha256=aOVm2mKilvuvT1efAGtkA7pi65C1NC3HJfpFxvYBS8A,17522 -urllib3/_request_methods.py,sha256=gCeF85SO_UU4WoPwYHIoz_tw-eM_EVOkLFp8OFsC7DA,9931 -urllib3/_version.py,sha256=-gMrKhsPiOj0XzUezVFa59d1S6QgQiKgQT5gGgyM8-w,520 -urllib3/connection.py,sha256=Zos3qxKDW9-GQ6aVqfBQVRM5soB0IjHxY_xXWpJaFTI,42786 -urllib3/connectionpool.py,sha256=sGFnddXYwlx7KC4JCP1gKvdNGLNK-YTCJDdGACGj3Y8,44164 -urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -urllib3/contrib/__pycache__/__init__.cpython-312.pyc,, -urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc,, -urllib3/contrib/__pycache__/socks.cpython-312.pyc,, -urllib3/contrib/emscripten/__init__.py,sha256=wyXve8rmqX7s2KqRQBxD5Wl48jzWPn5-1u_XoQBELVc,836 -urllib3/contrib/emscripten/__pycache__/__init__.cpython-312.pyc,, -urllib3/contrib/emscripten/__pycache__/connection.cpython-312.pyc,, -urllib3/contrib/emscripten/__pycache__/fetch.cpython-312.pyc,, -urllib3/contrib/emscripten/__pycache__/request.cpython-312.pyc,, -urllib3/contrib/emscripten/__pycache__/response.cpython-312.pyc,, -urllib3/contrib/emscripten/connection.py,sha256=giElsBoUsKVURbZzb8GCrJmqW23Xnvj2aNyQVF42slg,8960 -urllib3/contrib/emscripten/emscripten_fetch_worker.js,sha256=z1k3zZ4_hDKd3-tN7wzz8LHjHC2pxN_uu8B3k9D9A3c,3677 -urllib3/contrib/emscripten/fetch.py,sha256=5xcd--viFxZd2nBy0aK73dtJ9Tsh1yYZU_SUXwnwibk,23520 -urllib3/contrib/emscripten/request.py,sha256=mL28szy1KvE3NJhWor5jNmarp8gwplDU-7gwGZY5g0Q,566 -urllib3/contrib/emscripten/response.py,sha256=CDpY0GFoluR3IxECkM2gH5uDfYDM0EL1FWr7B76wtgM,9719 -urllib3/contrib/pyopenssl.py,sha256=XZY-QzyT7s8d43qisA4o-eSZYBeIkPyBMZhtj2kkLZs,19734 -urllib3/contrib/socks.py,sha256=rSuklfha4-vto-8qSLhSPmf5lmRPIYuqdAxKe9bg2RA,7639 -urllib3/exceptions.py,sha256=hQPHoqo4yw46esNSVkciVQXVUgAxWAglsh6MbyQta-Q,9945 -urllib3/fields.py,sha256=aGLFAVZpVU-FbJlllve4Ahg0-pH9ZZBQ2Iz7lfpkjkM,10801 -urllib3/filepost.py,sha256=U8eNZ-mpKKHhrlbHEEiTxxgK16IejhEa7uz42yqA_dI,2388 -urllib3/http2/__init__.py,sha256=xzrASH7R5ANRkPJOot5lGnATOq3KKuyXzI42rcnwmqs,1741 -urllib3/http2/__pycache__/__init__.cpython-312.pyc,, -urllib3/http2/__pycache__/connection.cpython-312.pyc,, -urllib3/http2/__pycache__/probe.cpython-312.pyc,, -urllib3/http2/connection.py,sha256=bHMH6fNvatwXPrKqrcn74yA3pUWcqPDppnK1LcKCbP8,12578 -urllib3/http2/probe.py,sha256=nnAkqbhAakOiF75rz7W0udZ38Eeh_uD8fjV74N73FEI,3014 -urllib3/poolmanager.py,sha256=c0rh0rcUC1t5tDGuUeGIgAzlErasPOwWwLiB67lX8pM,23895 -urllib3/py.typed,sha256=UaCuPFa3H8UAakbt-5G8SPacldTOGvJv18pPjUJ5gDY,93 -urllib3/response.py,sha256=9SX4BkkdoLgsx1ne6hpt-5PncrnDzPzeqC1DaShSc-M,53219 -urllib3/util/__init__.py,sha256=-qeS0QceivazvBEKDNFCAI-6ACcdDOE4TMvo7SLNlAQ,1001 -urllib3/util/__pycache__/__init__.cpython-312.pyc,, -urllib3/util/__pycache__/connection.cpython-312.pyc,, -urllib3/util/__pycache__/proxy.cpython-312.pyc,, -urllib3/util/__pycache__/request.cpython-312.pyc,, -urllib3/util/__pycache__/response.cpython-312.pyc,, -urllib3/util/__pycache__/retry.cpython-312.pyc,, -urllib3/util/__pycache__/ssl_.cpython-312.pyc,, -urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc,, -urllib3/util/__pycache__/ssltransport.cpython-312.pyc,, -urllib3/util/__pycache__/timeout.cpython-312.pyc,, -urllib3/util/__pycache__/url.cpython-312.pyc,, -urllib3/util/__pycache__/util.cpython-312.pyc,, -urllib3/util/__pycache__/wait.cpython-312.pyc,, -urllib3/util/connection.py,sha256=JjO722lzHlzLXPTkr9ZWBdhseXnMVjMSb1DJLVrXSnQ,4444 -urllib3/util/proxy.py,sha256=seP8-Q5B6bB0dMtwPj-YcZZQ30vHuLqRu-tI0JZ2fzs,1148 -urllib3/util/request.py,sha256=itpnC8ug7D4nVfDmGUCRMlgkARUQ13r_XMxSnzTwmpE,8363 -urllib3/util/response.py,sha256=vQE639uoEhj1vpjEdxu5lNIhJCSUZkd7pqllUI0BZOA,3374 -urllib3/util/retry.py,sha256=2YnSX-_FecMShD61Mx5s68J0_btUHZrrc_BkFVRS1P4,19577 -urllib3/util/ssl_.py,sha256=Oqe3rIhUU3e3GVgZob2hxmR8Q0ZDhrhESPlPP_GLaVQ,17742 -urllib3/util/ssl_match_hostname.py,sha256=Ft44KJzTzGMmKff_ZXP91li2V4WhmvEy16PKBcv4vZk,5479 -urllib3/util/ssltransport.py,sha256=Ez4O8pR_vT8dan_FvqBYS6dgDfBXEMfVfrzcdUoWfi4,8847 -urllib3/util/timeout.py,sha256=4eT1FVeZZU7h7mYD1Jq2OXNe4fxekdNvhoWUkZusRpA,10346 -urllib3/util/url.py,sha256=WRh-TMYXosmgp8m8lT4H5spoHw5yUjlcMCfU53AkoAs,15205 -urllib3/util/util.py,sha256=j3lbZK1jPyiwD34T8IgJzdWEZVT-4E-0vYIJi9UjeNA,1146 -urllib3/util/wait.py,sha256=_ph8IrUR3sqPqi0OopQgJUlH4wzkGeM5CiyA7XGGtmI,4423 diff --git a/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/WHEEL b/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/WHEEL deleted file mode 100644 index b1b94fd..0000000 --- a/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/WHEEL +++ /dev/null @@ -1,4 +0,0 @@ -Wheel-Version: 1.0 -Generator: hatchling 1.29.0 -Root-Is-Purelib: true -Tag: py3-none-any diff --git a/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/licenses/LICENSE.txt b/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/licenses/LICENSE.txt deleted file mode 100644 index e6183d0..0000000 --- a/venv/lib/python3.12/site-packages/urllib3-2.7.0.dist-info/licenses/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2008-2020 Andrey Petrov and contributors. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/venv/lib/python3.12/site-packages/urllib3/__init__.py b/venv/lib/python3.12/site-packages/urllib3/__init__.py index 3fe782c..c6fa382 100644 --- a/venv/lib/python3.12/site-packages/urllib3/__init__.py +++ b/venv/lib/python3.12/site-packages/urllib3/__init__.py @@ -1,49 +1,40 @@ """ Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more """ - -from __future__ import annotations +from __future__ import absolute_import # Set default logging handler to avoid "No handler found" warnings. import logging -import sys -import typing import warnings from logging import NullHandler from . import exceptions -from ._base_connection import _TYPE_BODY -from ._collections import HTTPHeaderDict from ._version import __version__ from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url -from .filepost import _TYPE_FIELDS, encode_multipart_formdata +from .filepost import encode_multipart_formdata from .poolmanager import PoolManager, ProxyManager, proxy_from_url -from .response import BaseHTTPResponse, HTTPResponse +from .response import HTTPResponse from .util.request import make_headers from .util.retry import Retry from .util.timeout import Timeout +from .util.url import get_host -# Ensure that Python is compiled with OpenSSL 1.1.1+ -# If the 'ssl' module isn't available at all that's -# fine, we only care if the module is available. +# === NOTE TO REPACKAGERS AND VENDORS === +# Please delete this block, this logic is only +# for urllib3 being distributed via PyPI. +# See: https://github.com/urllib3/urllib3/issues/2680 try: - import ssl + import urllib3_secure_extra # type: ignore # noqa: F401 except ImportError: pass else: - if not ssl.OPENSSL_VERSION.startswith("OpenSSL "): # Defensive: - warnings.warn( - "urllib3 v2 only supports OpenSSL 1.1.1+, currently " - f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. " - "See: https://github.com/urllib3/urllib3/issues/3020", - exceptions.NotOpenSSLWarning, - ) - elif ssl.OPENSSL_VERSION_INFO < (1, 1, 1): # Defensive: - raise ImportError( - "urllib3 v2 only supports OpenSSL 1.1.1+, currently " - f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. " - "See: https://github.com/urllib3/urllib3/issues/2168" - ) + warnings.warn( + "'urllib3[secure]' extra is deprecated and will be removed " + "in a future release of urllib3 2.x. Read more in this issue: " + "https://github.com/urllib3/urllib3/issues/2680", + category=DeprecationWarning, + stacklevel=2, + ) __author__ = "Andrey Petrov (andrey.petrov@shazow.net)" __license__ = "MIT" @@ -51,7 +42,6 @@ __version__ = __version__ __all__ = ( "HTTPConnectionPool", - "HTTPHeaderDict", "HTTPSConnectionPool", "PoolManager", "ProxyManager", @@ -62,18 +52,15 @@ __all__ = ( "connection_from_url", "disable_warnings", "encode_multipart_formdata", + "get_host", "make_headers", "proxy_from_url", - "request", - "BaseHTTPResponse", ) logging.getLogger(__name__).addHandler(NullHandler()) -def add_stderr_logger( - level: int = logging.DEBUG, -) -> logging.StreamHandler[typing.TextIO]: +def add_stderr_logger(level=logging.DEBUG): """ Helper for quickly adding a StreamHandler to the logger. Useful for debugging. @@ -100,112 +87,16 @@ del NullHandler # mechanisms to silence them. # SecurityWarning's always go off by default. warnings.simplefilter("always", exceptions.SecurityWarning, append=True) +# SubjectAltNameWarning's should go off once per host +warnings.simplefilter("default", exceptions.SubjectAltNameWarning, append=True) # InsecurePlatformWarning's don't vary between requests, so we keep it default. warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) +# SNIMissingWarnings should go off only once. +warnings.simplefilter("default", exceptions.SNIMissingWarning, append=True) -def disable_warnings(category: type[Warning] = exceptions.HTTPWarning) -> None: +def disable_warnings(category=exceptions.HTTPWarning): """ Helper for quickly disabling all urllib3 warnings. """ warnings.simplefilter("ignore", category) - - -_DEFAULT_POOL = PoolManager() - - -def request( - method: str, - url: str, - *, - body: _TYPE_BODY | None = None, - fields: _TYPE_FIELDS | None = None, - headers: typing.Mapping[str, str] | None = None, - preload_content: bool | None = True, - decode_content: bool | None = True, - redirect: bool | None = True, - retries: Retry | bool | int | None = None, - timeout: Timeout | float | int | None = 3, - json: typing.Any | None = None, -) -> BaseHTTPResponse: - """ - A convenience, top-level request method. It uses a module-global ``PoolManager`` instance. - Therefore, its side effects could be shared across dependencies relying on it. - To avoid side effects create a new ``PoolManager`` instance and use it instead. - The method does not accept low-level ``**urlopen_kw`` keyword arguments. - - :param method: - HTTP request method (such as GET, POST, PUT, etc.) - - :param url: - The URL to perform the request on. - - :param body: - Data to send in the request body, either :class:`str`, :class:`bytes`, - an iterable of :class:`str`/:class:`bytes`, or a file-like object. - - :param fields: - Data to encode and send in the request body. - - :param headers: - Dictionary of custom headers to send, such as User-Agent, - If-None-Match, etc. - - :param bool preload_content: - If True, the response's body will be preloaded into memory. - - :param bool decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - - :param redirect: - If True, automatically handle redirects (status codes 301, 302, - 303, 307, 308). Each redirect counts as a retry. Disabling retries - will disable redirect, too. - - :param retries: - Configure the number of retries to allow before raising a - :class:`~urllib3.exceptions.MaxRetryError` exception. - - If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a - :class:`~urllib3.util.retry.Retry` object for fine-grained control - over different types of retries. - Pass an integer number to retry connection errors that many times, - but no other types of errors. Pass zero to never retry. - - If ``False``, then retries are disabled and any exception is raised - immediately. Also, instead of raising a MaxRetryError on redirects, - the redirect response will be returned. - - :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. - - :param timeout: - If specified, overrides the default timeout for this one - request. It may be a float (in seconds) or an instance of - :class:`urllib3.util.Timeout`. - - :param json: - Data to encode and send as JSON with UTF-encoded in the request body. - The ``"Content-Type"`` header will be set to ``"application/json"`` - unless specified otherwise. - """ - - return _DEFAULT_POOL.request( - method, - url, - body=body, - fields=fields, - headers=headers, - preload_content=preload_content, - decode_content=decode_content, - redirect=redirect, - retries=retries, - timeout=timeout, - json=json, - ) - - -if sys.platform == "emscripten": - from .contrib.emscripten import inject_into_urllib3 # noqa: 401 - - inject_into_urllib3() diff --git a/venv/lib/python3.12/site-packages/urllib3/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/__pycache__/__init__.cpython-312.pyc index 06ab718a4fd5c2f6e291d44df56cccfe1fdaf8a3..83ad8aa3cb86bb85754287d8cc6faf69ae6c5787 100644 GIT binary patch delta 1583 zcmaJ>&2Jl35T9Lp*K0er6DMx$_#^8yO5y{KTPoB_Z9~#jr64zy@R4kw);jx=uG#gP zx9faxiIG5DK%!bu1svdl1Zr>P4*&_INxSn$KDSK;Xy6D$@Iwx@qD4fD;ydIDO zt{%_?JtzfTUC=^$SPJU{(yb3p?YX7_?SwumjkT#eOQDR0ff#4Bd_2@g3Vp{IE2kj7TRDNkU0TlO)ntdy#nU1QVM; zQ+JqSW^KvarLPe#XP4N7{Bqyo$qhz1@jfFJ);KqpS3OT(Q$|ss_IiKiNhNtV<>hxA zZ8z9NAy8ZKSbgn(*ixV4$^T-n1!`}4tiJY7Hp%uz-{VPAXPVSm+V!0Tl%E9bV_+3N zYH!1iZxyzgLdO2yXAH*Vs%dCV3sqFTVPMOi^-tMNf0<$Iw*M-@XSmaQ1;!CJkyW{A znAW@BOgFKnsnuuKOtjv_=&flH?O0eARZ~<@1EY1>LW(HY6>(eDG_i_AjC5m**wnfx ziyKXgL=l^YWD|+ThS=LrJYC!=imxD95p{zw2-~_zHdTG5*|ej4QGD)f>35ctSYRE2 zp0a7+T_|=>NsG_$&o#;UbhBeL)B1&T$P)<6K)uWrptAi7U?yF7UBpPnGK_EBDX)z zK+{sSB1Z2t5s9YtI-#D)!nBK$5d+XJHWJ=ZkPWO&g+KK)X+st|01;g=6Ql$AHy(@ zS(foX<$;+#A`p8Bu?~#?2FVWOAHhl+Ryr{I2o~C~(1D2#Om!ejTph@D2f_^h&_CW4 zfQz?Y_^|w5xy!P_b6p6urujg zjEC&xSLM&kM-XF%yHoaRx^#WMou9uyxerUfK%ylaK%xzaPt>o+4)T}U`Ahc~_u=}# yJQv#e3qL6P@De$Fyl{{^)6SiF0)D3WXSdASMrL#<<6oSEpXRvB0pHI&L-22x4si?s literal 7319 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 diff --git a/venv/lib/python3.12/site-packages/urllib3/__pycache__/_base_connection.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/__pycache__/_base_connection.cpython-312.pyc deleted file mode 100644 index 822b4ed7f885b5abd92ee32f23acadc84c186e1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6869 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 literal 22848 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 diff --git a/venv/lib/python3.12/site-packages/urllib3/__pycache__/_request_methods.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/__pycache__/_request_methods.cpython-312.pyc deleted file mode 100644 index c1903c6679bc8fb98af71a75c0547624ea3a24f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10611 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 NM11Zy0-c$j{{yJBYJva& diff --git a/venv/lib/python3.12/site-packages/urllib3/__pycache__/_version.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/__pycache__/_version.cpython-312.pyc index 2825750394693cda8ea71384fa05184326866259..ba52cb9442433c99e914c8243a119f27bfd56ac8 100644 GIT binary patch delta 115 zcmeBRy~pT#nwOW00SGqp#$`DG>Bk@r3@}0&pE-bx=?p0hQ4E!gnoL#fhI&S3dPW9* vnvA!&ljA82v6z;>50-hAI-3kQ#{$jYNIW4lETD3j<}LLNY=p6Ca7GeHWe2QByHQ zVqq*3I~zZMU%?V7i{+^jQ?`mq44wGwlJs=Gd*6Fc_db8W9LEA&KQCT=Y*YY#7sYBB zw}4y=1mEBU0tiS1G4Tk2Aq180#H$1bH-hRBI!aV4Q5`vRjT$9tB4@3UU80RM;#Iv` z;NV^N%wQAmhS!{K_FKy$yiao57eZ=Zhf*Z{dTui|;VQw3vAlg(=yV)$q|EmoN2tyCSuR27f&PU(Le3f=wGMd?x3b=`NWRv65}U?GXpu@qc2 z3${_PwFCF5`?zl`afq-ijH*D#eJi&Z`;h8XVSB0=X>Ky+M-g_>LU8wYC>I^v_Oi|^ zpD%-cz%|Nn@-o@>3FI~ zQXINZ4rmf;zCZQ{r~Z&9G*uDmhjcYpcRamm9K|w7BmPWnVSXX1)D6;BH^M RynN7|LwDwUZK^iD{10fiu!H~r diff --git a/venv/lib/python3.12/site-packages/urllib3/__pycache__/connection.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/__pycache__/connection.cpython-312.pyc index 8f3208840c1bfa4f32906827a3b2157ff1f86561..3ae645f7ad2f8cab5abfcfd4c613f4cd45394911 100644 GIT binary patch literal 20411 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 z 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 Ju9XG&{{eODjtc+) literal 40113 zcmeIb3v?UTnI>2u01_Yo0wh3!Zx$(1BqWNIELoN)i?&3)Em5)_cFe>9Ay!c$1rSOB zv_uAU%kHFy$~tYSlWoZx_p#i`w&}5F;-1-^aqsqQ?CzxF?me?1CAx`5lx^jV=j_SZ zJv&gSbTabp_J04pb*l;_WINfNIcH`XqHf)~b>ENw{;&Jr|Jmm&m+<@VuN)o!%G;9k zZ|Ft*O1a0je7{7Ju1Sid*y2*cHe|Drza(BVRKlM2Av=3Ih8*na9CET}=};+qmJOA$ zr)$WCr#MEy{GqG6~Z(Kyt|;*}#_(@+!hd*aQBmZ6q}JR~PthguVD zLv4vQLu(T4L+y!V+ML%W#28vflwyP3ZxK9JZmw8ti?rQ=@{!(%?R zD%QXIhW6nZj_*$#7&^ex)uK-i4joJ!8aiZ?N+i`j{`+Ps;ZqO4W#j&#Bgp?%5pF)! zN7bY14y9z&QHQ{xVMd%+{-?{&6$9B=6#h8}-FtWKrX z8cXR?+K}htZb^x_CGk&Lr>yykbLbgmz0!`aLsKP@?w?W45nIj^o0v$ZW9hNvL~8I& zTdpiUmQa(EX?*gX)M6v*>Db7*lT+u_pHYV*v^;w9nPYpSyN~X925%JyPM$n=K#eJ? zwr6aFQdJyRzci_)(nr+v*`$(+xN_dzv6On^#NoY~meg|o-CFXTI&p05ys8K6nM@?6 z29gsK>IhYX=V^KOQ5kzLj;QC^XFt83uzdF%(~=h{gVC`wxytATEp|S7o`Rw(l}L3u zpVZRP(^JvZ$XPX^MtnK{Ks=dJm1D_doY$gaZ{mD<%6#|jn~cZJ*T8{fDm`#EHgQH( zc!HXP6Df6MQd9NzJ{{90#wO0>stzYdSa&`Zi%;rJ^c{&^Jg%m-DW0@yP`zN*pNIL- zh;(v9RHvM^iGK`q`kFfExSBeTAypApJ(h}&7~LCHwB-5o zs)7$KmQW3&Hm0em=-G7od^A0Y(TGP%azS&nqsNY(IC%2l(ZO70N=-*c$Kq=Ad@?mg zgM!SQtbtU80aZ(njgFxgc=IFnTp+q<@4j894xfykJa}a9(NiaLKAy22oGa7Yfg zB3OEa${p@0XPNZ$$N&&(UmtrzH3~E8LGLHes}o%jSxKrXii`jHK*^})c~u)xC(;}E zc*=Y{-n4035h>!*Xq(eEkV64E2Sv;|s4H_7#(=Svm2<|E7gR0h)xiK? zZuXobg>|KEr!4y5q^NxuzWZl-&n6RUFQ8w1a-?@pa%3`r0Z;X!t1*vy4<=9crV=qN z{dDX(wf8x7;cxb@_szZL+@`7BbABr4 ziblsK#?sN~ba=%i6d#_XdL4jsN%|Xq@OD-Ee8=`%Rekf`zK=p{ZtQs{^e-F!xM4B0 zbJp>aI~%H{$33sqw0>^OyN>U9{=~D`xM!*H&_d&(TaAZj9Ur>tmXVh&1LN|xVf?@!AEpn&8p60;a{Zq zJ#a2bx2tNdq`$Flsj6e4s$;%#?Tyj7liz*j&1dG0yc>Qm^uxOM>)!Ky@Z`Mz=)CKw zwhKuLdWgzoJw#2ydXi2^Z`uaml(c<#x7NrV1(Z*#RHxcib<)4YC=Jc?OH$S!ob~YH zjKzcx)Y+`Kc6g|`ajL+IpI9_DY04HU8Qh0$!Z|`HK*Yg1D(6x!jzn2+mXYtdSUgwC zf=;KXFwAqk8`0V7#Rp76{y{RvJgV!Ub_hAH>~ZY~0;fYO`kM8_ z1ima&SH2*zYHA1JkCd_*m9t}lur+_bd~70Zz*0?3Pihl62SL_IiS{&7Xs0Nd?%zm$ zmo|vUbUpR$=V=QwFu7;-jI^T^Ayn{9d^*NH6(FMG6BRdIjckE+^&AEW(PHt>?~BD# z>a%a^75E&YL@KnscmQvWCX&h|kd`PQTl7nlu{d8ev@n$92v!dRjQ(iUf)XRL6X@)u znsXl>8%;jRye{?_h^Ms2k+Zg+oF~X3@Wc9;_dcx*w)V6wut_sw&hZrGn6lNIjiqfb?2RU=1Zlj zwfQpgxunXDyqkRGQhCd=2flkDyVJ`@kjfjuf8*!nKHxT3A;oYT)d}*}u9k89Mscbx zrBo>cA*=rzbqmbp)1OK;a2vvbY8Y-)u7s6Zr5qt1rB3lMtfyY_64s;CD?U6clm?{& zPyfoil}5#n@Bps`>O?gP(7#`&J|b78NvT9FDwSrX3b|G(ElLp2px#F0iV~>3dMTq` zk@7ctDzXwnd7&w1q%~Ky8WW2JT{?bu@~j3#e?uxZ3N+8a*V)2m2p?7%+un7=tPj|4 zEWtty+)O?|+duFqg{4TJ1y)5xT97umOYJ$+Be%stro?GaX^UV>0aA%HSlmei`UvEZ z2!fuG_!t243Pe(%hYfg4_T2f8{Pef7ZSoAk4fO^wB>XDD2ne<^o-ei&sM!PYX ziOG0+EX|e?+77gGk~21X1BaYOHPs4EFly+;*mJS5IH3+Srua;>ByTHq zk&)Nr1g0H^*({gl2A>m}8dtHKV_4YIpjjaZxYU@U%IfGS<^X{qBRfo^^w`K`Jf=Nx zq%gHr)Q{kTK~7$Xakzk%7LF8~3VQEY>X;(hkP|`};`5pcngbPyCzA@H3I!u^ma(o< z=2&oE7Mq7-6C?3SwB!P&6vM*w^#dw58Ff_9D9-&d+jNXA zU-SfcVxYVagG+EWIT=?Nv5pDMrjP>e&j@PT&EOE4gjlLMlf`Zz7Chrm9f?hVWYdO= zlpbBxv%6#k96~UQVj8U<^eL~99`TbaWy~Q0lA>~}pyY!q^@VloXc9-yT`;?4m!dG{ z6(Bv^N>#u@zC2imp#S-@rzMm+yQ(mAQLNk+O~Agg*H)yluv`F@bnu_zSUf0nY`oK3 z47|Azbs*%q;FBeDp!MR;#BWf?ozB1S2hGQ1i2gEFix%$?pc4p9Q)8a{>44IA&o)WYj2HE&5ns*c_!`qMe2T$Rut_hqI2a2d zT4j6%Lm?UzZy68XLdKgkUN8LSQyWCtUFN&+jjtCz^Qn}ak;Ew3h^j7`#vWqjrw1xw61s1Ze*t4J}lFPdZm zgjCMS08h@#9>f|4)Q!|}BJFV`&}bs&%EwY-Nowm@z^Il?L>bDbt)+l6oqMg(a>$kG zP+c3QH*nUH@tl+G^x7E;@$uqxaHpN6FsDWmg`EBTWIE@cm`qss^r@Ve0tC3vl@aU! z{K}OBSI~EaT-gORc1}}Ab1q|?iU8m=X-Ch!(+2A9v9<&Qht9{ry0Pkis4xwUPO@c^sq@hHDj-9V3Q z)B2lDo947x$D*q~+Zdr|-93=o;VT!u(fo7C=9Xu7Wxauy4_-d_@{!9&uD5^aZOPja z3`$&XrRa^>#wM0b6zK84-1oIUeD_tpeE9O=CGVOA@0$7cE$^IO^lrb?-u1?z*ALC^ zp-WTm~&Hraaqir@Kx-6p;qPDNm>uINfLN{&H7`66upW zQhgn%TnPwX`-*kR7)TMfMpCGpJ)S(1b1LfT$upK{#Af7c*so_C)u~OkBEbWr^`3Lk z#}tjL;9$C=s1XAV(~X6V;0scG{XdW)fHfyb;F8Ccop$ZNNXe$5o?#U3Lt6dL;lNH`bM$6SdunY zX0i2!)X+{T-Yc3fZJMf)?-1zV&v_6oYlSU&qfnp;Uukg`OD zV|V4NkTy2X+0YdRNMNWlk_@_5r?+THg1QD7Y_7c(6fhTrVnaI*K`G}D5QAWNnDGXN zhZ%*8)o;{~Ljf~Cip?trVwh7xRIEr2w4I_TR8o#_M;<3enVmMWlKP^%p=+j55-mdW&kPl6nK>TbN&wb`uX`*aFT5 zr~L#MC=-TRC_Uis6SlyB&-4ND(I^3Tb9BHZ8l?FKt)(lrS&`6M#bF%)>2;tY- z$a#UB7s+7~%p>Ic9XJ5J3@;)`qJ0&PkZW?OnLCPdJ^@&YuM(zk>R%%{fMqTA>siMg zZ2j9WZ=d%x-mYrB(LUd$OCs|Que&lE3}4%Rb^EoQS9jh>zcKy#^t+E-*?B8CFuUh& zqvQ=Nh9YzAi=p1zUOyol^EKPvZU0{6Pa=!nJp^43T|V^E;XD4|O@HT=^tI=&K7T9R zIq&NH)xB0J!0?p4HcOfBIp)24K7w%J%a_0WbEy=C1GY|oZTfaiJ7l2Q&=;4T_HY%% z3bl>-GJ9p?T|!p!K195qB8c^B=kAd7XQ5rTfm-{YhshnWf?xyL_+4}XlfNO&%HInu z4+OIY=rPk|Y)Xj1K>4Kl40Ve_lZ!kx`d>GU?AYRs4T&xSe&FLQ!+G1;yO3e zteye0SY;qC9f@g*YQn(N>PU>3Y!|?<1SnPUP1Xq_#xpiy7~eIF90(T#6MupjW=4Zh zE%2Z@pIfll0QA5|L=}luYVHmz8o>dpzSU6c%z|~kEP|NET46&>q;qf|ME(Sor~4T| zMv2gMW33rWOeVk#p9b3(IzNKBY?h#Jh;h>TP`gfb_oK>-(s_ zGh5r3t^FLJYC{d6YNHIOS}orzmnuWoJXby0&}m9@@ajSRaqZ~UqqjmGe^=(HtjgCR z*ZZkjRQY$Cc1hCvl6$~mf8VofD;|Gtx4|s{zKF;1UxIq1KoBXpf6ADU1K{gp4rpU% z&KgKY3~P~zpJ-eYP~7a6V}=xnGfLQ2&m|s~3C3mGtA?V2bS1_Vh)4K%v=$>N3ZguT zLD5d4zMOEi?j@fj0U}(j6CP~(GhIl>QB$n(RSfI`nIcp@h%v|sN>YuPP_;1%SVsj_Qz#$)r|Ht;_}4LsV7Gq-|0^WGjliD=}+&p=52F+vJ7v}`7S!x(`o zlbH?1bqTbz?9kOJ`2)MyQZqyaQJ(Av)Wo3mv<@H)MR*)0&Gek4eWv`U98->nb9&c7 zE;Uw3Z&{9wKzse{d6jla7UkrG)M!NIR-xatz=~Pc7Q$lUZj@O(W z!}2uQznz&7uZ*@QYd0?wH6^T1~(xNr<>l~CNz%1;BtK)RK`Oxas#9sOo}8Q&+t z53Bg3*i1v}a-Ngb6}e|@Q3LTM)}CD{%>=Oa%FMO*qAi15 ztgS$vJ471uDd6QyWu`P!8KoJ_ei=93T8uXdZ?bqx?-suCy~1Zc6^ML_iAoT<&x|E} z2t6SD=9BeV<@g~pMEEk5fYgp#md+7JYp<)<5suSVaGmZDBy zL7ggQ{1$OoMX^+)20u>oq|NcF5HqVOP3Gq#fTV zeCG4qBUn?-pPKR^GllT6l(rX97hYe~=zlL#bAx|r)|Sn@`cIsBflr=g5qE4GfS7@_ z9#b`N!xD1LKva{&d}o>!GG}~Q$PYn*reio%1gb2Vz_w>mIr)qvZ2?uxbAbv9bd%^H zf=TQ!5; zNl=E+-yq%@&^*>lL@Ww~J~?5bFomj7Qkg2uWKS#&gaT?-DyTuGL}KP}8nICuC}#kR z6v_+G3j-Hayq%s(tEpj==%r1bl-EfAmUNLV%4?LccSSlP?;%OS;h_!jv2%!SRTZPW z^m?ySAE9q_#7tKpA+@o|R2svr#}rL6j=;#LaB^hBt}`Ig3u}9DbOR|KZa5N4kDTRw zPfP_0BjtKfydq0dl_Fgb#tQ(E0lh$|!5K4^s)U{bE+!I;BopW3&?5oT2GS&s<8$bn zDVFI5{ZPh;h`f5NfT|a&1yKQ@#+gzOQ!bMs64sUG^vKj~Ac@Hff{CoNjV%mo%0-OA z^F=tvN34g_h8ZJ-3Cb-Mq%4ln!K2|}>L#e7p;)TH6jY%|M~t)(p+BTT{fRAHe)z11 zCo*~GMXSQ-5SCfbamo@bAT}u&SE-mbW5ED0%|>Mm+GdRNL^?50%o=62gN~I+z#45} zs+LS$Q*@`%7|Tj$I5?r>&_hhnAQJAHDja-L2LwNbtAQCM^OSnq!My-;EeVb|*T6*k zR`e^T-b!JE7+acux{klXm^V}pgXd3VI>VVweipTHERj%^G0@}CI+J(BQ%OUki**Z^ zDqxTbh8+SF^cdFxThT}kVfZJ+;UjBsrBGFfWeRv(ebf`8|NB-?y;bAgE%Y!6Hjo~y zP#tB{h3RGq3aAj2q3evJPTfq$Rl^G9G?Y-9(@73f9~$$wU;^pT6Kk9XdWyspLb10C z;89l)jxfMgprl)ni&$V{eH9SbmJ!#%+sN6;iF4|TEd$Nr3ZQx>yptK6ra>U!3~8uzZiQ+~&9zq87#FIA$gC53urwe- zMOaVIq+6QKBIO#@*s)d@r$7XV>8&R}JqCay9qT~w@K z%sR!OiMi0J?>eh1qE28$M~s?8`7m3XFUxy*92x=Y8(E(UU@(uIV-syR^@IQgq_V0X z+Du@J#-hSLTTng5A|PlqpwF$td~We=6!2=4)OIoP!N0J~VFv7E23}3D&a^Gwv}-qj z%WAKa^M`Q2+sHYnkF;sLL3xTVn4EvbFy|@?2O?Kfyg!&Cnf5KpegMvN15`J9dzt=* z+}4o-ryGB5{1NHWQWWtOD%poMg|p}AITD$0MY1s3mW)5AMs*k^*dp3*Q!-))YTqR1 z56B_jgZ6DWxw4}t_7Y=ABjLU#lS2fGM)VTr)t!MaR|C$CC{I1Y z)ffj@BViV2@RaX8cKqncqja=CSH5@P=-}Y-y(jjb%z24>B`rQpLHB zU!_!~9FOM1{6|!I8eLX0@3uE6mQPfP)kT}5C~uNO{J@+?@Tq`g zOQd9SPl7CkKhDhZMH3)4gl66})9t}T)3Z8>8iW_ z*|v*jHP?%KS=;i~~RY$>p2A+YAgo(}_&{I;;WhJ+W;Cxa{_8wh5r>$0^i^e6Xb zTQ+1ncjhZ4U)?=aqay#1RJ&#=+`AC&oyGaY;LFc{?RnC)X}tcWn|}Fr%~}X3uCJMI zee6B&yU*jiXUkEr+8`;GN+76!V54hx&r65%r4DyhHr%ij?pz3W-U>%%4Hb$J?Y#}X!q!*gi`|dTH|<>X_usCSZ&ZHE zbHxcgz(74Rd$nw_b@PAsh3{{h43g7CnG5 zAT_L(1#ioZU2~h?jn8{q7QN`;N@~3uG9>~I?>ANMc}#kLW8Hxk>CYYu9DG#z{$u{V z57~cE(Xw}g{RbPIvVpv(1Q#S1c!Xc$#IwBXjzaNjBD;ZZ9D6)zz_*vN6BGL*5;d-!MQyY+) zDZdHHVPn=?b*H)QwXs*m=0fwXhFRB@_HRZoHQe$I*N3jiYKay>bJW+iOtL2uDHS`N zY#JHRY*Qr`Wh_Iy^F_x@$&7u*G2?_DJgIp>{~QdOl8oa8pGAP-$dtV8G#F40^r*oA zMm_YOR-19AYCc8p-cVbcaZao)o=>Vi|k%Ny+yx z7W%yS#LX(ui1BTQZ#Q0Sx+yx|Pf8U<#cS3GeKc>sjVv&4Aqw%o+<&?M`kF;gGh}R- zryV_WBi|mJceT!zUa?)-ay@jzmaPomtZco$b+$C?@y~l2ZU@_LYXMnNO!-VOZK#hTH-oj$OCuMd29wX>r;qh>te%T&>cEI`pP6U%upPwFzhOodYRsw?AXitP1@Yo;bsld8#7 zX;rAt=S5ocDaL~JY8B!KGeN|a#>qm#il4QHXhl_Od-{ta%6Lrp%%|c-ovJbK96U@{ z^gnIJ5I$;&E?g|ll#P}_#OUTiM%E(Vt7W`JCvdLpBDC~u(r?v|t0J}e6s?(Q%rsuH zX%uVJ{3?7(1>1|76#oqMusuy8aP~7#YK@;0Vdm4w9lO^!(K;)B+B4`kV{VuqGmTW2{rH6y(eNs2gjwqxpIZGrQ!iQ%QMNf>zPvi7Qi)m6 zC?Lou&Xz`+Eu$oi#(Y^ZTdH0y7qcZjBTB(cC<2?ue@lEfpGr{FS3GB0(6SfI7{Z4> zDmuS1EuszD#Q)Fz`-~{teEu5!8^T%yazMXN>|M4NKfQM|^-LIT2>#QCs6S135&MnW z+a+z=Of6Q%4iRQPGZbC~f)?9r0ly5*Y3-%aa&Gzjx!VFP=`j7}4b)kfRH&aVHQ>J#N!LsYurp-Kzlm!Cs z{UVo4tugDYIW$;mc38x0paQfVfdAXke?@v)+ko&vk;Z(cTGLO8w@gFv zw8d~HF~4RSCfY1LV6DGdLt{2--t=Bkn&Qqhn7MocbZWr4PqH<{e&bIGpZOG!Nhv3E z=WWkhM2X=CNah?bvXVr6K(gAIY6!yFY`S89=?}TjI84Tq(WNEO@LPjgVy9R%mn9y*M04oqMm;>=I=U7^^5jp_6lDl4MtA=~=;)iwV1>yWfMTr#b04flnrIL_I z5Iqc)Lu+oQ!A)jkMduEn=ncC&Br%7*i_>HAv9wh(`VX2FY!Ur~W(DLAWR~MUXUz)8 z7Y!+Bm%geZe1VfDz?8gMS3ZDPO-0n>6xgF1AHY$)f6%h{2Q3Sse_$y1{6<<9LItEy z{A zmu#+yVv!Q+MA%h{Q_H44G!2-IOWNQm02)2k4TUcfT}8&o2Ir`fNy~Pw?Oobz}1sM`%VU&v9Rp3iZwu?8M^3jj=03}k#&P=en zT9F#P7j*IGs>c5(sz^hlAmr1l+eG*$y2j`@hya+bh<^5XCs(t;XWf>TuBS=EU(oyP z^aDdShli~tLaHDP2(lqy1I$hyA49#Ew4P)U6Q)%~+SPSs0EoYtOhVXV!z6(Sf+=$_ zcu%+B+LO~t@ zf|CsgD*7{bJk!19y1B5FKx*D3sZ(V`hX^YYHL$2|4G$Mok(-y-(JEc#jKlfWTLfEK z{{`sgVZk%)HAuxRRtO0;9n)`gU_`U3o}(vVlBuxG6wh=h#VsphKh$??=tvfHz1Y_= zLt&cf*iG~Di&l%=*Nch$@8!vN#wRM3233P%t#HD8bLqp z``}~@;u;7j^>73wSEf&beHzImwEq?k%u^FzNc$slm@*Nm5OIy7|B-xi7Ii6QHRzI1hH5&fvGIr-(& z>^2lqnd|J(I+>M=GJXitYu#u_5sTT|m*+jISu6eQ*uZ&3-vcT$xoLXz#{roirb7IgKlKbnTo6Gd48b%D#a&(f%Y;bD$_Ti zTs0kn6>~94=9lR#gZ3`PbW_ZFDC+U^%s}Cy92JR`TsX=5&1#JtKW$;OZ$UwYQXM zY$z}{V5)3JYa zW??TcJnw1AHmsXHd>85-jW_lzwLiSj{_r~^i|u_lHhW{sTyXBpt*Wi_-mOp)gfXrf zsVOq=s{5#=pA`3CjOMyysW!4u8<}6f6B>eY`Mj&~E^L0aXKR|M4&RuV-JkUaUmm14U66$X5(wtjz>+yY&o6m?znMsZriQkmRVQcq1xOXcj}v$>enySubLQ{K}qp+h>oE$(wao(o5mULO23NO0F@PFZ0z_pjLJ5 zrtT!vC!5!E4a&P=`dljooA1`a2W>|gCj4g)-Tk6X3N_y?m&l}!ZW1Ef&~jx@w!LdU z(E3w983}~hvx=(~*V7*c+JEW~euS|mCCI?QdryBbGJoPJ=o3OKvs4PnSGLfBcwL2) zY8GaHwb_82_Xk21&{nLIvrQ5DL*?pP^~yETw`Qtb3;AH9q9VUmYU;SzcyO+EDYAbd zvj0}(AO@H8=eocj}t2l->z9Tyfm4Cqs($o0jSiFVr8tRX=#8j1&{Y zOa4ta{hQuxU+Q~ep$~O#MP+wTWn0$17JnuFt%)n1Y;y-R5i7S|*_RDBUCUg}eB=4K z@KWT7g~$^(!%t*u*SzL`#Xnd2j^o4HhoO{MxfQ7!)*^pLsZY+;k=uA zqn!E-UDdjY`s_1x-o3R_L(BDxOD(+%Exk)En-^L(zjNeP%kEqC1GE0Dr)tU5u;6L9 zzVF6?H;%r3^quIUXD{a5uhkl6NwRCvyGC!zm2)>XFM472z}k-B|HpRZ+jX_sBc)PZ z$9K-WJu@HP`E$u(Ssk!kd1K2PeXsYu(f@isZh2{ZzY%BRtGn(=CDmldq`Lm&yc-EV zzSqyJOhN7U4%B)NW#x`}Zxa+vpzyY0nakh##Zg}N=YY-{qBZql-CcZaZW>RXVe zMg9a;Tj=0I&Gn;D!|l-8+adXOsPT5FHSZ~{th(oP2P*DXVU2yd%I#I>?aGGhd%v^) z?PurL>{zUP^mc8>{MsGwo?_~_cgSieX&}s{{qW3XRg7r(m z?wi5xY^ad~DVYAshU&86mf!QEyM6M<_Z}~dz3ygk-2-FaPm!4L`;EIQj>*yw)>j^_ zlz#B&TKNC!q3TmRrGMR4{nUEtN0p&tP4*vkY(7?NpRaYoKi_0WoO!wLl-s^gKhS>4 zVPEVoJ>``?bT|?8q1#S=Z_lYd`-eNbp6asyb*GKo^<7Wb*q5qpa3eL`uo?+vnIfb1 z6!{1dxF6z|z3F$qi3?n&@F`~}yl;YH{*)X-4ez($02wgVo+FU1;rV%-5x}X=wDzn> zNyx_iy5o#Do&g~Pc1VVfjfwAHrtjsaGzuotQOi)?d2&c6WV-5@d1Ii6Lj88XTrYXo z#1s(drzycEat^|Q$qQ=Cw3lD{pyzDF!RN2y^Zh!y$EI+%7e%nRpF^ZKd0d{i5vms; zuEx`Qi`QZuRlud)z+-wXl^|{aMBe(b5cmr1crXl&4aXlMnf8B@V-{A=s>`du5IPpq zOhc&M{YvxtKhX#pA>NfV3e#RO3ap)bOKlL?WopcA$Cfq>;TPqcz zOZ&2iK6mNJd~p9Q$LFwBHpbwx%Jw`H}2~P1&Z_Y+rx2dt*Ml!`XkQqB8Hq z69Ca)moFoqi|!?IldoK=s?K}JN2ZJOKJryaRh#mD@&%--_4!KjRY{c%`5^g1Qr)(E zHTi0Qld%%8RfL+C>kzg)ZL4yI?sc_0yOs~zBF;J})8m$-UE- zoAXXQQ6to?jC?Mswma`8AMQu0SoV-_qqL@z8YH(fJfKPUx2(x_uFp55N}RQKD&)MA zJjkgslrLjm7iH>Z-f{`JoQHY6)F>bGqJ7PIKl7r2E%{33t&*y0@Z(-dd@yDPPCD^-^mGuK{e8mDevfQRwoxv)s9Exi;i{!j|8n*BygV*93BaT7cHH zcV%mvvb~$BO)YEkEi+CuA`r|w$)k4%^TMjGoOd%XhGME2LmJ?`Pfs z5TAS{^P&+g`5^O#q;PA#nt5xaa9cjiytUNII_9mHf*bM;%-cwWO-!dG9%`Ds^H>DM|LS6I>eXz(|?%IVWm zDc)IW)P`%Xe7N??r(b(jflIG&^{3)z-#lOO4k6Y>+mH_j7XrB;?G%=)8%S&rYrxuM ze5@06JORQmA>3PW(F8??Wd>qCvut#JW~112y>A4)}Xscl?LW0VccUF$v9&fl{f4Rvu>dT zqZE~0QbmlLK00zjw^;Ivo2W!~Eit#R$9UnB=W$1q!j)|b8RAf%VYgJV>>%gER<8$f z?q<$OOpnxb9i6jhSL&^pM9;EaMBMsOZ7e5twn73!%(gs_!2jB2HI8<2oEO^H^q0&SRM|xgfnHa5fcIHN`a217jBp0{tuIeXeTtii?CfOKm%v!LeqB znM~0u!-^R~!62GSb zcWyEp(zpyXjKsycSC=@(@HirH6Y&-(LO{p3wBe^1{M6SG?N>0 zzaVIJ(1px}2PYKk?*=_M!x@rr+~O?b(dt!u-hLOgJ^l0*2?T-!&-W+ z&5Gxmt@JKu#j~Y{-W#)RTjk60qhe#H z2<&8Veu7RHmEdHbG26`?pwliDJA;Q%W5!!z7rh7pCw)j>fgjeN?USwqh7DvgBlz%M z@M}-U$5OCOBg57YyUg1Hn`InSOHQ6S%QY_Pu=n19lResfl)z550rzGIsW~ZW77j+r zI5VEUv0F&=`+>Ecl(3H+V#@QIN}z`*ENg$20)4y|I&ZhY#x}!cr`uKlF+R@(N1UlS zRG}MEgfElnNOrHr_4e1+y|PZ{8(p|rzct&q=JJs{jcv0>VBqs*|7CwRDC4BsQf1ph zWt(~3z*1=KLI}vJuQ=@7F8%)+UQh zjsGyWX<%X*;L>OT-R6?C>4xSF<7-UJ+VbG_<~GXk*dP z=Axl;(a^S{q3uOO*A@*WB6GzLtys4r2(OCsRkAMFz4{9w2Eb*@rVlfOi*^+71d835 zB5)#8{J8?5wcI+uFHwn{Sb2%c2`e14-lAgPvSsu1zyP~xKret0QwCL-(KI1mCS;3H zDt;Rl;Ze*6 z1}F7>_?q(^Opu{;rZ2={EGzf7DdlfiII**I0G#u1OS1fOckx067J^}7GfX!X1;gS> zm{x+o185;T75~61VIE<&2#Q?^(|oUdmoY@y@O?W(41 z;1sjy3NsTBUbw)Bcy0SD+hG?20zipa zc&+!9UWkgUFL(T{=It{9x_zdR{;apoQ05SgLE>}m*{jcjx?ZYVw@|lkMR0B7Qf=2l zZCAEYUTWO9(6|xg_EO8%g_f-_`K^DF+n3~R3-UG)^YlG#Mh+bsSUb;`qUs4 zDE=?Bfb-*+!hr`XBjex83{T_fN z6_{5)L~C>ukqIZqy-f@!Z@!*hbhU#Q1c7b;*O=TECSqP1L3$&dpYxp= zLG$mZw-{CPQ}Pi~X>yV|ua*%xKc^2F<12%_57R4yh0I4V_ThGbEC!XR4h(t<8Fx65lg6Gq1JX2j9PeEI)pF3A||j0OyGK{q8dEl-hSc%ci(al7xzIt zU@%L;YFxXHn2+#xb!Q*mPJjKovylziwzc{7z0QXjhZs*xG2#%Dk8_C0$2r90;~Zl0 zaSk#0IER>goI^}L&LJjWjTCIkhsj4A;(Q(X>ZMS7zJYv=#Gz~=AH?8bI+9P8YS!jk z$=4<|Z_ckFUpx6a$hTIie>C4ozAmY$Gan(}I;loxt;ly%85=092R!6o^uo7%+U9eH zmzz7C<-a^`3pp$9N^m$2lR9^VdR+Vns56$!ci^!yTr$Sxo<)tj&`oAe7<#F}!D$Q@ z{}luHuvu>$r6ltz3NwqMRalH&IKY*}ap^IDYL2a$W?OKOdd0VhGj>TZAotUC#xgE; z)^N9qrek#zCO>k((1>HaNN~VFd6J?mbOOhq+DZ`HVC4ax<1a=xqiE?7FfHlu9^qa% zzDB?t5T0rLkllKas89-ofw=^17g8>`r2-5{LRuJ>sq5gQUf3f@XSy>ojb$;VvnLGXe^*nlU4wWXn$632jynr*BXxY8k&?G$yXl(bcDM z^RV^OVX~r1RC&?6htUI+G0;~O%RlK~U!QqGvcJEdA>#5*3qBrYihStJu1?xz zvB{oECeOsx9vt54?Ti?OQesU{3GHEaXtS#mN{gM{a;J#MI1xWPhI!8!6U1MLIJqhz z$pyGs9R|=&)2C7EOyEt>FZv{79 z+LJBwT?yYTtG(@q?$m;R`y9;RY=7r8eDkjD9|7TQU+{06J2~&)_Rewm=3U!Xqk<@( zqk^anpoY%e)QF^bvxGN`*f0eKtu`Ql*&ceHnm};pl9b)~_^*|@)`Bi5p6iR2TzB-Z z&XsBkKxJD#4^JVW1Jn@4UmS&`p;qo3%uy!ER(l%(5m6)EXA%k>Ltn% zx%BQN?}R=kME|WpwAy z{vJP|V>?hYrppRd&k2QFczSRL{SI{yCBe*r>81MX?Ketqc{(rcxf_sN-Ybr4{;U2Q z_QgQ^T`=&Z=N z&h!lYJ*)VPxHUPJK8sbp9yMLbgM1xf!n#}Midl*)iUD@`LOu@$wbq3Q*J(UDk&5BL zUjG&4+PSOeV2{fEru$vT_uPNt{=l*5IkMz=a>4WDyyHpU_xc25T}3^~`u;_DuJPM3 zq~a#2VB=m-NjJzcBCN<1H;J&9I;>aK6&oEK{0Hj#49F#679ir7erOMA^3pB}sEJz? zu|1eWz-KozUx%~cbg+&jY5O!{&<~=sXYu4Z%3mbk*Wic_B@DG-*^`ohM$T*0iv#e0 z7_Hhqvq zH!d%3Np||~r$}1!;@U20F4%AXSGIn;4z;yYR2ps)wc^e?RA(TZ@Mu0C7?#b{DHv{D zYN0y_AZq4!$`kps^elA6cNWGJdrGwg;!bxF`8iO4(E!yi!Vl4C`mHRs34i4r#d!Igf>hPZ3hmQ_Cp}j_*X$>)YkD)d& zgbBt39hGA6ku7hAQZUHJ@S^*!M>$`#U)ziMhCeCwG+eA-o9!o3+rN>#Kb9(fOm6v) zCEt%FKVE+#t^bMC@^7WCpU_|TFH79El3z-2mdW`=NV0AJWl05v3FjB&`$e;4>->rI z&`+dC{#JVYCsNZ-q=)}j+Pxs{{#z-6K;((QJ9hVyJ$%z1{;{Lt-#XU*Z;pd^O2e1V zER}{AO2gM(w@OTTQeeNu_* zAGN&Dl9L`^wnM`?D?JXXR)qEIVg1Xcep>+Kd`J4@}#W`GVjhj-6?%(>@r&$;K`pS89&OL+dS^>?rLD3bK=^uv1^+``J6VM%&d z(j-mJN;!E_mL)Bug|netcrqeOA?bpoMc$UQs2+WUmh?M z|KfVaZUlBy0NcUX&A|2qM(Jei7GSpquw9IO4%lA33u8oFZgew#8}QrpF8_NQ7^?ug zL*Iazd#yJz{(0bc2KcdwvAclX9l&m8>>gnE>O`M6#umo+0Uy`5_+Q-0*lz&4FMxfH zvHigA56q>Pu>-&k>M?(WZHzqt>@LP02lj;CruX=B-p%-vz@G}tV-I6b1A9hq&?A29FyqexKU{_1 z%lLD^j|4{PW9)fgFX(aoIe(OIF#aO&qoBmo{(X#n1=v>uWAroj60ny8*!}Z~_?6E{ zQQ~r`U7fbgY{Ai!nOxqqoX=!|ZyPmIW-g!A9eqq!wUp2+0jUW+E9(K?m2_!R)}+ah zCQpXvL-BB_>Egu1)e*}wt>VtJs;w*ft(2a3GNz#vY+X~P=M^WNv6Y;u6|(w3;$fr| zOC}99rzex8=44XfQEo{lzgSm^O3zpph}Mh1B3VGgZjx zhGP%fskCWk2S?3`K|7~f&g<$;eekAk+#JkirU&!$PTDkH9C+d2pq+8_{dqNYL!H&_ z!Ge`V{fmP>vGzcI-fF~`7{pMp3A`=+TV(&Ik(kxyRjr9R>xen8s}{(fE$(3U%!yyM znPbs6tyTj3!FDQjcz{qoRr`FZsJ=E;&!MJTWLoHLX)>aP0i*Mgc(l}X73>q@8(by6 z8Pig960EJHRF>sj2BI_+Rmr2%Kq`#ky@ytF`n_@y;9Y4!UXYSro#ceP;`OLks{CI- z>-S_$UI@J>zaM_&ww0xMDIQ7`LqiI*EJ9yo(UA6JjEs{^657_YGu8%-Rcg%XwoS4n zDilSrn0FiTquA-salq@l33z)Oz-?){d-I*hJ58U&IzEnVc^uoa_|jiZ{LRZtJC1!6 zJI+!_c_x77a7XSusWav&T_^0SN0Vnl<3v6F5)b84Q!<&#sEGVBNSjcF7({xekYEAk|?NJB zu?{dRm|O7(3y1Z|+KG=?n*nU10SXn=s13pC`AS7yY?2m~s%s;ZDi%nbRSKc36ri_&jS$;S@d;5Itj_Hs2OWQanmZgJr&zajj4yP zV)rBWwZBaNIQ>CnY5Sq2_CrgtLqgjuQvB5_r4Wt)y}nAFC6zirDs@<>6xF8i`j4+t zb(~9VeEgD7p`&AJhTO?qS~nD6m6UEdnVC!q%A(AunXIkQ%48_C;*KjR(=hZDd7*(s z%|&$&Xs`(M<6ZHV`eA=jrK&1YJ?5_vr@s$D1wG*%X-3A<8k>yVk#5P8Q5b)?)O^)4 zZ_Nwx`-9{=X+1!o;+P6nRB9_GQoXQ}dXQgQISKHm^1G7bF)gsHE=WIw^<&Wu&4w1j zeC$g#Njv-n^l#S_2|(x{ zHK`J+$I5!a&AM}X%G7ir!(%}iss+c)sZJ)PXw(G$U71GoqbZ1chE2mkaI$}5KJV&g z9VH}kEdoP$eI*?6m5{2|L~kp^;Fu}Tqn`{?Y$JXm_dTl=4EQqXqE9qcRT1r54(U_1-Dnhw$deI7lF?G@W%bsW z?pVTP)D{RQNIzLkPQ8`M6>^GE$W7~(V$LWQI%IShzs-TCuIbu9b2F*uP+qmv9EU1H z%0#uvQ8XbMIV2 z^!w4bB3~VW!JYcM7mmFmiQ;xh#OE-PP3%=D3KY{UwkUnWA~Zyp|D_zqs@EW9!rnl5 zRd+Sn078XpB5fD0)mut}zI9`j8u+Wp>*!>YpMrDU_}rby57+(j;;aAs+EVAloyPx& zb$=Y|eH`mu)b5{qu=4>N?aRXAX|aA7tsft)3XNVS$_QM>Yf~8Yw)A zkW{?}{SL8yhoRr=FPgS9oK}rlT@x$Hsi1ztcj>n>wnJhkeN$$2$5vENj}!{WRf2kE z)=<(It2#XLHR2KoL_P)1pa>jfkKf|C9`;I={}g>!%bs<~Gg>zak59nLG;&oY){9ki z1TJTUutyC)AJ}59XJIF>gzzyYF7DbFpYkm8g!ochw$Uh;X93=td{?Wv6q)O zZG)1uu*?#1`7@fjb&S9&nV%pW0ot$F6hxEn7%fM_(d|OwRGGr-KVRiGIwmqX-7Glc z4x$qw>KB5d&LhqxF+=1T|c35>%>{NJ@&&w6mA)CdP5f#d`iM3(~OUKX@ zWfp?pvU%}5z8ZaU3T)BbKJIc`)lE5G1AB66fQPHiv~$EX0tPY7GwVn+_M}CMHVGk9 z^_m#Bel_LA^Uq|SU>gG1OqQHOO3&OR9ne$T_NVc5$v;wr=);I;5J^L_*Z=5scKEccP8{Xmwv?fLU%6) zX`d637m|NhHQb&R-iJIaep{c?C3D1pY8M zhcSN+RJEo#w2kEQ&OF5tg8tzk{UXPrSOo-USV<_<@feQSj7`KakyNv=X{I-uHRlle z)YcvH2o`~N@UD2ebJ-`Is@6n$`*7Aouv?dQH}+8w1?xy3xsy?6@C&p zJgPFAL0*ya5?HkJ1*Zs;W&lC2UrO$DFO3562@VpZcq+WxLd$ z2vUz|AxS}jf-u3(+a`(kB0R*1N%o67-Jxbp}Jv-QKaZTBOO zV*?N75aN9*_5ngFgY>1zYcdwWDSXEO;EaBecYU?>a8|6QHU!O+8n2&upq% z(@Z+X6Qh@fQhIYZ8k~ct7S*c2u@J?h=v|pX9hz?t%>+KC!d?Q*bwaI;)^j?t4w`PH z{52!R+r1#caFVJ6^~pQL;hd`Rc3K>CgTLaOS;TUlF`9f%t%~fN=6wU{#Aj_Lb2JYE zzrefF0zf&UFgBCT{$`S@*EGXU&yT?44yOyo4P7f9^KjU;=o*g6Sj$tQmK=v^Ax}tX zI2Cvjs1#0WsoJh1kUYq;2>cfnUL?Q*A=H}4-F(*6=0WO3$I0UyeHfPqpxe;p;`doD zRIS))vyjCF0Nu~5rqO&L1;1xFoK5v6IHhDWH+1gVC(Z#4WhPJJv@+AUu0We+BomNK zDp=V3SSmJgJkK~gSDJp(k2qd&EIHpZPT4 z%qL<+(Y*LCJQHtnN>(>!kp!AlA}rlt^fI~g@QC7Rtdk!(tVW1MHiCSexq4|_T6GZB|K zsLH_s5Kmn*NQE&e@1Zb`ey&7HV5AEQ&@zJ#Hiq*YpKztvlR1K zQ#WqJA{@M~;zy1Di!p6F)JD`u2TsPK_Bhu6;PnsEAH`0wpsSAZX&OA0BPejdT=Ou$ z_QC56Cs+B9zc^S;Pv7ngZoN1-lprSdEBML#bZ=g9oYNNYN$Vzo=Kuodv~<0}((@KB z13W!VQGt^^f1}fWdbwr&k1pN4ME@?|z5FB^J|{PSE``@OKas*s%{=Rejr9}Yw}^%V z1Xjn+EFl8FA_}_zaH#fqD2ilwn;bnY|GFU(72~tkr_jKEeoONyb&iU3=rCEgu!!P5 z7bi^KvnGz4U7H7=-4mbzSCzeinf6QDzW?^Z(a5O(X+ z-1BXtnUv-wbQHGnVViOvr?Cf?Te?YYesE>+TOaoxe%yPw91b-#KWUJ9cHsU(t9#P& z|7bf0o~F5P7SWNIyRzG2`>B`{84ojoEohbTSftIx+H4TE(-E|tGh{Tc-hI_IA=u8y zQ?_HxW5}m$gT+Xok;Yq(0v|XZDo3Kx-e0%*hCxlL3ayb^Ru0`N_R&FN zn#*)U;AV!;-{9scMV00SpN|jf`1|kSTNZ)MM5dSbJLm_ER8>WuZ@15*roP(?Typ5- zga7JW#tV$QtQxqEWwGDmfY(hVa2krry#EzBgW9Q#1c)HJUBKnA8*s!{NR!?|X6klW z3%wOGqFVUrEY-7897t+J5~U_eIAv*{Ry@gsaRK~BFHyI_-TIU;5x|2s4| zNa-~y+9(%?tNTBlqd}g;9}73=xaX=tkoMY#d0Kx!fbuBTBtT8`ewx+3dG90vpFu~M zH>|g%^WN|Le4Vnqq36!|<#io*PA+eJ?d}Csbi5`ncW(YXEO+!g2}`Zr<(Sme<}C&v zZ2Bm6lx^LrnN49532dXU`C5PMi&#r^=!pa%I#ZJ>wL)`#>S_JPs+su-r{an8x_i3h z?MH31V5Q($owQ{ZX4CHGB#+B8?j?YL3~Fx$?}8GGz(JaXXA>8Eo1m&H;(rU95Y|gM zO04K2Gn8=idpIY@;UD&gNnzGWggw-%UqPU&;M4*M1?avjcPPol!m{ zqZ%2=^*O?}y(CS5mo88A-dL*ewAO{#l7vun|ZVqjInpwV9 zA0rX%Mki?X^8S~8zW>ZmnjY^z^N%N%I=|_r1BDy=D$QkD2^^sjea-zAT@Zri0tn5e zCRM_B*;4oR^M-4NrO#yyjW+VM>k-mE>Ruti0Z7U$&>;xehYhO80dk%T_gpCL!n-#b z0HU6zO;yc1H-8MeECQ!NP+%ubs6>2#?vTzDusOmVQj0X9w9UKeJYZ*T4dl$5*wfOo zg?p&9`{IbLw2?5_z9p;U*HV*zEk)e8ZFCa^;wCahf|f#H4}my=g9Hu%l-jYMac{^l zEBH1AAFuPNHJ`%rF(w}e@_8DcOz{y3jr$zbGMa7<=t>QN?*o~i-f^R>(B6$)WH5F z;FGW{%fFEJ{HHWgZVt;$_nOKQ{*_y(yw@!^$^C@Kzw+iL`S87?R6bnZj5c@X-8NBq z*S(&ygn#Ar)Hfa&XOzoN+N3V!c6+&{T|RQ}Qdz>k@~#GX`@My-gn#8ubX)X3--sdb z8T>1E({Ky--xLGV;EVm0!IygaA7m;&@4XQy2k?tq?t3+j@{XmReKhuta(AQLyR<1z zKYGjE5&7i3(Xxbp~0jmlVyR|4f^pxhoPn}PB=x7+}7QN}+p!QKbWftq=@rd@8n z_r{WPn7Gnh-qj{IFZL|$8Y)XDm7gbCKo|a%TRP;S#cfM*@CK!FA8}*St%{BBt_9CDVe0~m}#&Y?hMJb_QA}| zO5%n#R8&EvHmTX7t(m$;6r^aRxo|a{*`x3rsA3y!metrg~19)UYZ4ohEz>QsL*CvK;0JKHl zdjZ|=f%Y);X+RI?`#j@C7=94&Lwdv= zua}{R0X^b@?q}#TfIjP)_tOkL3TU6+?)Gqip~nF2*AKYRgA6?m=m`(>5JOJ_dddSm z%+Timecl5-!q5Rg2R)-b!_Xl>PwR@l&t1)D89og784t!s89D;!3woPgdpXYVmjIstCJrZ^VCW>EQ=UFfGW0y4FMFV;7JcDY zJ|f}?%lSGrl}cNx6;G$kar|x=(@j&I(+BlgJ!S#WIFgE`lM4ym(kFCP`$!hwHJ+K8 zIyaC`rO*oBQp4h}bLn&fka}t}p_>cnTgUI zXf$6NjS4vG_0i~;Git(qQyqS$$NQ4Qccm)*{5b6NbVyfQ`u4MRhV z0ZFPG?M^`z7p1+T1;HXEIN8)Dul3;7*UI)w3tX3GWzBc39@-?R`L6}0gV&`i@^sat z6t2qG5K)Mo>@C~L*|eePw8y3rQ+Zo2$FZv^MO7B?Q6y%Mi$Nuc)(yYqwHHwNQ~51v zNnVno^oFk`AAZ?XB}tYo_ay0te@XfVmS9PK8`^Nm_qOzo|6Parq($krZ#?VkQ^J9K zfW`zBB?TkogJxzyH;Am_skjx5QWH~8%o>|8V7}UERuhfJ5~^uNqb3ciD9TlX*LW*_ zWn0|YN1T2Xz}LbketlJ1ZR@-q_sSqxyqT=gQ#9k((_yhi@Lub?y5w z6ya@&xs$`Ev@pCFM^M31NUz~perW{vl|v1d?t&EY%2gcJW9k}eFH?7gBSI-SaCx!;k571-s{OHu}YNL@p zZK8q%hOvPPT419aMZUq_gP5L3fVpL!7Ek4r*o#hTO2ro3CUx=aYtqjHM?VSpgT0^D z`htfA=~I&!Zug>v#`POfe*J|0<&17xg7}}UK>Y5anviCyrOZRjD4?ZuEUsFRa_1*T z%}C6pWgtRz-uOePFc%Qrqb@GX-R>I!(0vWj{gVG(8!Cg|jh$!-0qcjXGH5k;8N)%| zeX&i{HE)wxMq)frbqQH-^iIvC?0slyl9@)7-P#V44!I=VwsQHyQ1`=>>`rJD->sLD zLEqlRK@W7z`iZ{niSir_o)VF{0h8HF@Lof1 z+lgG$iCpM}AoGxi%xT2WYC-18l0x(?7L?eY>D<^$H4>*rgbK2Hy##xJfK(7u6bbhE zPXfN+?oUI$;7LKS)TD%99ntnE#TuHN92Eq4bV7}jD!n|frxZYym~L3{*?0`3r_8GH zgsG4yPAQ~tCY6}0^CROGb;`4ZG8H6~)*LlAAn+u337#%v(agOn#%6PE%0N2YFb*W;9+Im$eq> z=bSYLaDns16mJ%%9^ z01Me(`m~%xj}efHKHMfr&R>5OGWE!VDrw`^e+g{)6r{TIV+n;I0cu)80-r~!b%S~= zt?6Qqk9qf4%~ew@a@> z0$GBu3)u$jm6eV;rp^g3x+ZK)vTNp)OiI790LvMu&Z`RxdI}b7T-#>|MbT^6HuEDe zCPWP?MsT}U+Zll|!AmfC81u?l2jUB}5`^kbhg9>YGSfoXbEo@TFx8#4@ zr}^Irz8>)C@Sv;1eM>dziHN2(I}@D-Tk(McLwep zzFYtP%kRJNBQ@7@?t1kv9YeQm#k@KEVW@{mxWvMB#qV$5F5nqgvhZahGLODWO=6e9P60_`Fx$=Zft3Ie*9~GNv?P`7Wu+xzMvu}C!yW9Ng3!{) za!5l$myPXVu&}?#NBL@7);-KnXMw@zF;rLi61hCJfUiy4TO`fee|O;S;atme*Q;Ia z&rI~wP|s?|*6V?9)bn13<&XUM@?oN53zSgG*0DGT!NpTn(Jr`M^=MzKq8Tto)1xAZ z+k6HEyH+DW_Ov_<8Vm-vF%8l)+#`NJ^E^L6gP#Tg|5!pHXpnk};dZajvl2E;#glqE zV@)EMW!nl*d&z47{uGfG$Sj_^q$c8;Vp3CK8Cv4w$`Q3P6Bi=fUF8)!Pb)^n60KM@ifhuZe1Tv{ ztQoyo*P1n#Bb`0{*mV;t2A3wD&>=u#di)aYy{1}_AVxZ=V5N2Al5SK+{}te8P;m{n zyW!^q@&sQO@~st?FMH~LkAKYexG(|sVjhS2tAsffzwgET1%WxiOEC8XfUB};=o*3) z&$A6dy-KTw3l?}+d?-y98=4D}s(<~4UQD+iya~?a9 z(l2{FwTN=wM4&dYs0d}Xz>3f2568!Md3YKagK%+1-s*~eF;rGQk=|_ zGNWkI!^_}G@js%kcTmxaqQualy}&K6{pO)O4s{s2sQZd$%$ACnIkwE7(RaJ4cnCLF zwNBvmTasnVVyR+SQIm^NSIze~{-V8#Fb$cJWfy1%R_rtx|J|hbyU9&Q#MN3Pe?ik1 zz`q;?BPqp_ZiaX|nZ5+&jL3xT2q>2hM)ni&pc~r1ZoD%sh``84q}P#tKokZ6`hu0V#;8LSQH?BBBO03fDYZD|h34 zKIE*Ou;eRh>Mzo0RQw@slWbDf)Ds*8U85lAgOF6!_$yeDU2A^S3RDv!gg%Smb}u4Z zT)$CZv~?VL8svx4i40|Qgv99fN({2{6w29P!<}Y!{HC&VMJBLq`%$2u+$kis$(<@o z6Og^**>dkJgJYh#deGhHQ zy{+%oe!KS8;GLa6?0K&zx9w!E>0~Y>VxnNKi!>6mRFTbS;j>M`&Wg;bakiiy6M>zh zKB)LB+$JR)m|LFyBoIIZw%!*!B@5P|HYKd_c@}HqP;$XqJO|MsD1X39`6BhD7#W0y zVAWC-#jyjBO=XKMl}6grK%#)|L?V3|QI*Q}ObVzORD2)z8cB1?JdO#x3BE48At@7y zBHF`Vw4vgW;$=HU(-hT%v#?!e@u~3cAh~A}uy)BVLzWh~+ltZTF9SP+io3X7Q|u*y zI>Fb4decB6jrd>@`LZ>4d`$!nbz~#$O;Bg?3z-=agF?j4RAzK|gfZ^y5r~_FI2C_K zl{6KkR~fV}oLffI(0`DRFRO`+?b-Bu*Bpp}0-!64@x{wV8q3NtAXS-*BV$cDBxuBi zjP(e7GlU!!@8fnC`m{ir;3Y`=0l+1%k@_f)2x$IdCfN1=19^8M=TyVACvx075jgxT zK^1uVW;_{BsD_<@fW?T&98^1PLUATB*JB43gc5Fftsj-54tU4+y3gbPQyLfyfPlTj zo)ims$oJn+l=VC?GQ(akKLpCE{2%F?CsD90 z-*t2T=5(%QzsI%j_-SbS{Z0jLeFJX@hUF1iw(Y@(B1ZHQ)ym`ZmkCJ4Kj1E=kcK~n z0NehtghK2wHHqPNFIrGs>)PXHY|dnCUQg=hQx{X|%c<-E?+&ZQ&0{u#GY$clSy)IL z6br|33w#;*?QLt3g-uY% zjE`=V#!R!FRvP+BO)a|Lh%wCUQ|_CuP(&tUMk(F%dU?BXDD*}kUY7pPHw+Jr4^4~= zM299O&Q9bTC(jKHjGT6Um!EjKN*Ka72nvP??qW-!%+pQ1S1$ zUDe$uuq61puxuZ}Ni5(AtDA$W4zj&oWRof@W#&zUNRSG~G(_^(){l=+Azve0srUtM zSA`rExDtF_xNZQio<-mlDQO4Iuouk?hx5GRgOb-ccV(!?V&%oWK?qXuA6^6x3Iqwh zJ_IKh;b0~yR^}jBvgszj=2#wU=ZtBm{GiR?!m^I8jOQN^o>crNRlZ6E+YSU;7m6Lu zexFOI7Ez#s=!kbEDd7*UR;ETLd-(uBIyD=g%NVTT2z8tgRMboGeh{Ti%?Fg8i#h{NzyGq zSeyhDHc>JzV!>%XNvMk4A0iE63}h-F7u$T3rbfjY-PI^4w&{0boAhQ~lWTFLE$Dq< zK9jnrYuRHC48bv7!>J&qn3#A=;x%m`D@kC8UdUTa5NGG;wfTq*`D37EQ1Ka62B=_? zBG9_f?R-A1EdtdumbCz88^GC6&vm;vB6r z!3*Hni#mU~TO4~zDYFZ-lSX_dgYZ0JxCm<_tAZn9redgw74kg8tb%CzQF~o>hab0X ze?n8H;=ie~Oa(6hfl3y@4*)JF5a%38eds(2olFUkt*M2yARTtpYP2dms}`%nwFWs^ z*&p+~oT(}aJvE2uyq4C@RJR2ZrGUMA5MaPQurSK4LGsF3Aa{rtxvv(y+&*olW zAB#Lekcg!q$&nE&OUnAf1sJkcTuodM%`T;p0f}M#IN=6Udk&mE^BN8%z~O{0x9}J)*+K(lFbL|7kUsKC;P1X`GB8PQpX$`Kq&Vqf=lEWjF4*?mc z=qo<>^A}WWp@Mm)jcP>UTm%WXE%^0SWSxV^IyXv<8-8ze6~)*Z4*zT~gaSc<6}OL} zse3swwlxKyQo9O=h<)HEH~9eDv^w81B9eClBm@MjWcS)IAsHMtV39ZoC6%W#8n{%=0bkj*3l$ zQ4=cUh8$ia7p_q6vJ15+VHcY|ANltZ4fcATf%wsl>~6}c%yXTxDwpDXGzhi=g^K1` zPjD*y5k|u;!jg);geB20#SIS#AwcjF-24FGT35WF1;^#CES5$Lo@UsXn!?{w7_2SX z0JYtA4gpb}Jx$@LR}L7Q0Wy5hO4j6yyD<}=CcQ2%$yafz^mShfDNX)kfWSNc&x;V@ zpjB`@UqeBk1cmUkCm2NzX3WF>LL^oo(&Jb@aAaY1asZC>bzg5U1+}PZqMqGe`n}hQ z5gjR6#qA4!5y4-UbpioSLRZk(8>m%`Iz7C;t|T}_n@S20Q74?zsBc;R;!o-n${U5LJBezxX~bY^be z_G=eFh4ukO&t6n^&b~*JpyHI5DTW2+1TVqdWs3cHQywTSIW+Z%psC9))E_t$Gw#&I&^D2-m=Yhd+c9C3OBdk)DfOw*N?oLM;fk!5oj- zI08XW6txdO1ovE#O&|W4Q{>ZpZ~MS6ev2DZ!GB-?(SScMaCV6fCi=(o)lLX-Ll2^S zgd5CBI)c%s7=Mc|?LRiiS8M#YW@f&@X%(ebhJrRCJ4TWRHekuw!%Yt_j7!OU4iAO^Z-P zOJpBEd`7P#(Z7f93@Qc*E65=cuKU6R$od_t9eW=5>3IyN@$lr6Pzs(tdA&uWmZ1Gq)YGeKJ5IuXAeRqX8T|cYJ~V(V8?9-+N;pc!KKh`O ziUU-TEaj8%e4Lz5Xy=>Y*4cj}$=kpOHTj$zpD5!)Qhd~iPwjA8p0kaduH(cPXN>Z- z#q1AfX*iK9Rz6UIMbszit86l~}Rep#09NjrWaHT*06Z@MQvbx%5V-(T}Hf5%V!9rprz z?gb+E0#DrwY%kQxa%8!tAmLF6$#TzfH`RM^l&>#RXM=Jf6q0u? zcNHW&3hlCdT3!y*o6~qB%Ompg2>lw7AJj^%%GIVqeY<>od9)zmQ8*Biw=FNxr`zb$ z{uM^HzYq$_AsP&iLbEKtBrh-B{2l7%CAm;9%P-0+J@o5E46#w}%60YJiWel*3j6Ei zQ*!PJyroKEvn-Fx%db+~aYkptMrVS5;T^tn-VMtuPu%3T!+2XQZ_jn@El7A4+N$L( zxsEXX*ivW<$R}t{coZ6C`PlMB>f#tpwr?d$^**W}T4|#CA*vq}b#5~yucYb46vhb1 zp_Mx7DMUS;mRGjis-<_dbo6e~e%C73F29;njua%+3Ox;S?Mhc}XJ0`=t)R&AL9xaM v3!z4N$I8}R7)ys*VTUZAkyoav%QHNs9V=Zli5-PD+PRgHPw-|?OB#wy_7#41>m%A`pYtBO(*S4tu`Y12kZB>$;YsRd)#+i@y6aT>{Le_$YOlB!bAoiPs+ zx9YX`%zd1D&pG#e=iI}>)9*c2_4~9m2ZDAv?U|k*6(e+;4i=mJUT5Be&sSA-ORQvx zSbJ@)HjE^%#0}$CUa}6OR=Z@Ac**`M)9R2NJvocpm%j{>T@sQUml$)d0ga&$6NcZ@ zRR|5BB$Aw`F)0B)TTi~W6Bn|sotjL;Hg=vtfs8wJI)L?bu|tVP5~|!5?v2NY+H{8=3k$3gJ{!8H zpK$~|h^6Fm8U74=UC^UfrBnhLLU%K?Y!EzB z+Tvvp?q!z!nu@=rp4h8hKVq)PBMGb5_dBSX}MN)V!s>!3Q{X6oZEMEPMeQf^AgrYb@xy zZ25h1%v1j#i$`%YDn%;2b!cFx>NdLwbuwj$uo#^{=aOg8S@r}vg9E%+eE9>*EY{w( z)>s5+S|Ps%BW*i>5IePD{$adMyTU)>cj=C>5{@dW7!ApUgEMt!M>r~yzBaWlE^Axt z57)CtPt@w1+$)A7I+swx5k+^)(NIj1+el}qwszCT0IOS~Vy{fH=mAzDD~X7zP#_5H z0&!~Z+lw10PIBR=d<5j{A+#VMK4ZN6Rp0CWm;Kjlfs`lfg6q6%;`ofGe1!YL>7H>{ zUp#TmU46}2t@}%V_V|={omTHy(`<7*f9AO}_p>kXC@o{`(CDEXu8L__#l^&A*NkiH zP@|q(@UlGRDjRD2;z8SPIXy#%zg{TTMjeHs!@Gw^SNQZj`RtWE478suudVH8PM!=f zglhgRK6yugZvfv}1D}3d`S*9!)IK?IUzpICFe2bf=+Fo-5Q>NS0Y5H3^E}%3^D^OBnP%Bv=p_lm&U3Oe)H>r`~)l-1$6hU9FNGsx=1V}MwEwxi%iY1Mac#N=t`wmPIB)!-*UcNf5|XuV--=LyfWhErWWk`RCj)2v`GpUAsqy~s?HPzN_aZy!e z5+xNdt5|^m&Y_uFd%vSA*#RuaC~q4O3VsH2+ZL=S-863>Gu7F-e+H+1D)KWQ+Ko_JO6vy}#wi;Vpuq5>b`A`m_q}?4Y1_%R;-ka6Cp% zg`-_U0}(q_2feIjIi!YTQDJj%V@7u9IU`mUTE20vAyTUF!Aj_T?&n?k{^u-cvI2!>7T9HE=I%A|)(eAj8YGu~v2A8+h3yEl$=09pK7`5K`#^bU?^;7}s9f0jvuPlVR zMIwd(Dxf75NYddl34J|`i~jY;g850f+87CO?~y6?Jb|j!0q_b9rOtDLhDTYDXMyu2<-mMR0Z-^@I^} zE1VD&VsQ!}F(N=7h=_3KfEiI0!itbkz!z#vFrO&{HRcfps?0-`ps>4d2?~h!bM~^=D_sr)&HdZLjPaX*hQ{CDe>quJeT?O&iM1!RYct z?sKfw5Vltn3bHdOD0NOvK$_Lrh#Vy@ZGS< z{l7U297Vma2<`3k@`9^xwOy+#s)zWk)Gie5&<3ooI&oF>nr@`}2C!jWfF=7t(-o0M zJ!;z0;9q~>3blpCFx+}J@?_pfFPly;yVx?hWhQ;&6u;4oqNS1#7->06&zS!mkPD~_ z4L}ARV72%Ce(cdcE6vva=678Fm;WZNF!Dhou<2Y_m3!eu5+SifCDHl@)xKK}^+sWWRl@x4bxz95n9kHi!i{}tyKcc-@UR3jT z)w>P9I{e<@PjatrJ~&l)=(^|dEC-7icB~D)Q}9;7jlj<7z|NV#t~tco4{Eo{^PhbC z)Q5Fb#rv;k9+KZ?OjW0~4XOAUElM{_I=_ONqiD{z+)L3k^tneHeQ;uO=8Z%KU(8RY!BVAs!0~8kCwuIc~QGqk#Ci( zu*hp~Rs_Hdo%(eq1$q;u)ACPXN*H zLDL9Wn)69Jkfs1$e(`3^a$K5wp0(9D?joDr0Y^ac zs@iM{7=NZ{nlDSO+qo#O@+x0t_zLRMrlx>PbF2>tubIK-&X$Rl(}$q(SC90W3q?lNwUpRGa}*Os;rty_YM6eMj(Pc!AC1>P$J0Xs0&_I8*y*gMYAu zilY%kYJuo{TnwEOyJW?D_vznsq>>WqblsYWOHe}T91){kG7O>A=q9?=e20wMfY8TY z_dVcc(yg(M9;mAfNi8C`@ literal 12020 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 T9W}nCugc4;_XUgf)ieDM&;YB! diff --git a/venv/lib/python3.12/site-packages/urllib3/__pycache__/poolmanager.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/__pycache__/poolmanager.cpython-312.pyc index 3821dfbda2ae3bca0c6c254a2eefc5813d03bf4e..093a794377af013696932ad597546615bc36fc51 100644 GIT binary patch delta 7549 zcmbU`3v650b@%c6NQx9CQj{f95@r2J%a&|O{#&vfTd`ByvS!Ja!z5;Cy(fthKk|J~ zwx!aeIs~24Cb6Bn&dHL_UXc_wVsuWmtOeTUvklnR0fQ8Kkif>B(-jzk0fTHgXqp!2 z&bg0IvST+b_5hxD?z!iF-Z|%<&*hzWh2LHhT>s{D+6g=}&f_D#?h7uTi7fq-a3K`V z8YNT0oU}wNNo&L^5XmT+6JpX9u_f&hd(shcaGE7ikaR|zNms;`EQ}N;-4S=PC{o1x ztcl{JC*t9_nD8b`A|)KRC49-!NGZqdiB-w6NEyc+iSndB;^%llHW5fxL@ER_L?q{T zgh-`<$fl7F-0Opn9DGaAD3NN(6ua_%FC59rjY|+?gawtlI^_zALh0AmHQF$V&@D+0+;=^f0 z)i7K<=RleoPs-`>e1pm=jmt`YB;W&T&W2nk66t}_Trr<8Jx&MYXiSo*tSIa?>j7J? zkaxh!C&e=MH)5qzn|oM}NitQ~Z^WP{SA;y#F_c1dAe|bF53#5%#Qw^*yTIC$PAB%u z6JZ0bWd1*?EO2mP?bf*5t=mJCmF*V!b?Ig z35zq&I}aF5S4r5;K6ZDpZAJT8t9_7tqo};-80hH`{1g|=G({HIuy=|+FTNv^f|5B$ z)%nu^W=&N~gyo8>vU&3)OB6<2Br=Hz5{VcjA!3va5fl8(l3Ox9Cqyig2~5E%nE{GY zkz@gyO|nAEE)`26v>cKRS_RN&hn5rG4tTqy0-zR3PJnL71+Yl+NQKZ-JYlefy|e^Y zj@8bzd!2^tHoHBTe>yrkg2`AaHYC$vdk_V8P-}%!<$aH9HD5 z?Ct7nSQ~5@R@Jd>&CNsU^iV=>8b~Lb8y+5<{o_X`WAVfzX7~dWOvHxM=|`=ekW-;# zY%BlVCjN0NyKB*e(mjwrtROE!Oz?BSZ|S=LE)sU3uJU*W3`F+~0?8ONB&zF|s8-

}`jCGl^Ps$^^M8bcaP+1caOzjmA@PH5$zo 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 delta 11519 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 J*$s5;{|B#^WCs8M diff --git a/venv/lib/python3.12/site-packages/urllib3/__pycache__/response.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/__pycache__/response.cpython-312.pyc index 0ed81b15136ad0d282973f6113f5029aee2f8eac..2396aef8d7cdca0341ebea7eed458e310263e8b5 100644 GIT binary patch literal 34152 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

dA{VdaZ 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 diff --git a/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/proxy.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/proxy.cpython-312.pyc index c47b9997eda39ecbead6807e069ce9dd39adc487..ef888dec64439a7294b858eb7e77ff33d0b2c101 100644 GIT binary patch delta 860 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 Nz}Z{Ne_X>s`+qTFp1uG8 diff --git a/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/response.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/response.cpython-312.pyc index 9b8f7b566373c4fdb303c7f2bea6739429046525..08fa66ff829315f09f7cb9dfc622eb61315a1302 100644 GIT binary patch delta 1276 zcmZuwO>7%Q6rS1HAOB?SICdN-O;R^b+FC71rBoD&5LA+o2-G4Gi6TqwYU|x3c6Mzu zGX_l8N>p)Z1X2_BKn@&w;85fWQAOg!0Z|Sh4kSvXB&$^Fp$PS6CBOkL%s37$h$qea z{ob3O_vS(Teuw&#ss;d~HT7El>lL*l4-Q^q=BK~_2DE?;GZ1oJuml@rkS%6JW+Owi zBwNnN4PCMn+n4b(UH%Yc0*1nP(C{(dQt_t)Pk8+_^uqdeQnbp{)N*#Ii0R78)qds< z&QV&@vR2M4&@ANGCW%ZUw$>0lBfy9|gnmTe;1l;R^uj`lDn~E_=KV+dQ%QqIx&vy> zs{fTYAyuEE48cG(z8|CNza!!^5r(QFpCjNQ2js(UE9^Q=jtjsMYKN(yD$;*$;fS=g z=?1jT{Z?!r5Ua?6c))?<;I@<%Wv{EuXg`spxvDcC#nq9VNuB2n?N!KgXBpxCZV~J&A z-XqLgk(NDW&eRPPlSWM(*Kn|>T>U!Gm9-0j~C4J}UYwRNtZy7SUk{hMtU z7GHl9ODta6Rodz@Xzkbzr#8Z=t?#qqT0DVlB4 zxuSvN+>3Gr>fZOq7UV-O;RBRR9`+&1dW}s|Di(;T(W1utiO({?HMYok#uh<6214;| zwP!=^S$uU*i7cP|EVZTd?u6S{BTF~deO)`j$nBA(k&i}K-`WVK*2UBT%LA9wsu$3- znKCV7Q`0a+?l*z66|b$NXXo` zBX-d!TjnSpW+wk|gahjcLiiAz{1sgJ1)O;ZF6_(Q@D;c}It<@{2NqOef1}9XM1e6* delta 1251 zcmZ8gO=ufO6rR~%Nh?{7EcyRWHHj^3L?}HZq)nUPki@}BXbc5~B4q7Imb=obGo!XL z5;CEO6bg;mgA4s3hg?c}2_YrW98!8JJygO5n&ZPP#1}U@Fz5ZkC9* z_OD$;pUGJ%kFwIcqX^aIm$J${>hbuXhk990fzPCxyd`})uAsV7mum|6n1=X>N3AI} z{H@|`;u@|(Aq<7;@`os)-u+r##K}qWQJG{pF{jr!FI$Dx6nYh6MOBcO+aE(0S>2juO*%{MSSF4;bS7w~j0oV*x#Yfx<70pkm zuD(VxCZUXq1G>5ws2!B)1~CydEd~t>QY^z5p*+3j{I2%};_zz@Ci~9$OCM{D9(xeI zpZ|X1e)!@0&EfMqeM?Q>(v#@$=5pJIf&+WTRLhv!HRAh`k-f;_R^;$bWcIrLn-RN_ zzop({pVxMch5hlF>&xv)G&H$wZ`psUT3CA$j64dD><1&ev7@`MUTH>Gp372y;13n~ z!)+M|n`heyjC02~aVD%W4R-XyiK){` zn!UOXm+xwn@Cq%t%BpQ!us8{e8v~wf&aJFm?n#Hvi!kM)T_Qj`g3txD5q+8Wc5lO7 zB3%%*LT(_P&Q+iQNvA1v3%P~}NoK7ADM9fAO9!2xv2c}&M@EGRs5sg;h$aLULV4kP zy=a>ii=3pdfh*o<_tb3}WBeGs@)S+{ir#sO<~!OrK8-skkK#-Cc@78gTsO(zeU~lC diff --git a/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/retry.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/retry.cpython-312.pyc index 3e8fcc996beaff35f373f70eba2d59467d179ddc..77473a11b47db7eaf9f5f49b748c07287a1be1d7 100644 GIT binary patch delta 9383 zcmbtaYj70TmG17Dp7(nsjUJ;>>p2=dMnWJk;$?v(5Jm!vS1f{uk-7ni(TvjFLP#F% zh!oif+q*z+cCjnOk=J+)1-0uG`H^gPYwcvWYO9jUYETl*w#f!psNJ%wk`jni64!3k zp40Qt!;r+;D|7nZb5Eap``&ZT{mvb~|2D1Mr2RjzT1^y`JJuJ6D~?dqFR+mmue6OTT5r+HW1P_1i}5{q_+@zhlJN?;LUU zyHpu{#66PNpGQ*+C1{50vI8tXg@Rjjy4CLi83S)PMe)YJp>Hu6ZecC3i*9A!=r3ld z-4t)SK=EdwKqTvyNm&ZmU03@qdMF}%g!2uMx9QPF;A^Cz zbBe7`_`;YV$$N$*IVMg7SVcD?$fskxq8Sq-3M+_WOeFuylx!T#j*idFhf_;8pmCKF zsD7HK`WZpP(-)}RCujvNDG%hAF{^kQUbCXD6LcU`;9JGg@a&4VUeKeOR^3Y8gg#(U zOrbtO7AJZHIXsxzA$68optzn(y%aguQ0119p_c5H(d5I;o)-^NH<{kasl(Dx^c3e; zbp<;*yV?)$I~3~aJhW$jN9gFD?n9mXx(^=mb3+ox3!|bi7?uT|3rBfwd?*s(1_VwN zMq+1yHx%X0wgj7l++j)J{N#U}rHA3Qs3Z$v{-#zjjEBW23?(UAtSPRJjJly%^k}+E z@npwu-?wl7(aw&{JPzK34I7L|5`HE}j%tL2QkWH8JJrd`<#l=L_f&x*sT#;M^}yh` zWW8uzWN7O_`hmswSg$pj7EP4HyFbxXK9?lpHh*viKowQvhfMl@pSQxIKrpw0i&Fe=7I1yP<*1B#-P1X+f&4DCt8@orM8csk}3AQwp`{F7b=a)C;* z)`yIatzXhmI@==F79Et%x4>2<*(&U5z@BRCX}}&E@-}9r6YNH0)-JHYBpbx8P1x0h z;+t~f{;W9bN1k(m^(0vjO4njfp;}AN6KA*T1hxYM%MwFV)R$MB!93lsVMwL6J;Cd< ze}WNe2HpsNdfq6&-$^^*6T08Tn=erPX2HUj@Rkd7?&P-eR^Z!s8&ErM2kPLxyaV2F z@=j>Epyh&=n|DJikIw^|&*uXz;7j=e=<)E|JkTsOQpkG+%td z&|fk`oum7`2dO}r!m5rC-kV2$$bN@zBX8=yztFvw-j z@U}LS2|3wvKorgn#m1z_1Sg*cq%ay50fd1jctPS+K*pUok;YabKx48H0myaYL<5M2 z?8APIx9C@%%MZn7^w&=iy+f-rg_jd0X@Zi**kB6qAg*eo@0&A}FxZXkc3UsTmC zr^TbfS@>*#MUhzalpy9_gb$69a&N)Pi1d|$MkDwpC&#$vz#Zc0(HOI{=ExgdF=0#I zChvH~#78DD_FfSWheLx7wERrb!&Ak2HPCmJireLE4>d-M>*D%VZL^#MBs_f!TnEb+ zX_-yeC~7#HS7aIR69bx7;Ylv?Zt^J%&U1?U{5)+djvWVKB{E` zdT6zGoC@fBCu8YzcpxU9=KSGEBsMPap|q<>e$`$4YaP!I7Iz8~9WYdI)@V%TGVg%{ zM5P1*b}kkLXC6Z&zgFib*^~d%YR89Q5^4}zQdH6e)&Khj21H7LgUx5d_nnn6ND)1r;cWrSQHO0HUyJ6j1cw>``G*RfMXvIV3$u zP?xw5$pIw$k>DvV_8~cl#WNL~Pw+YrFFUxjP}O{U#cvKPjAZOyXy&DuYSu9B9>HL2Xg)Q>ei>Ywx3$IytZ!U z`AbJH9{r0K?pn4iX_$J`jP@r6|L+vb76~`_`*ofUn_*R&f}N{LOW8?($)nTcZM+yjBL!PM2JUjgzdxX;?w?R_Kh{ zvm7JCYmgIX$oZXlq;$P0fwwO*ny6ahRMzi#b_m3-(|<_=KG5|Btbf4ho@#GsYH6c8 zhBehVJF&J2NFb4~X!)T*`TwmQ95RH8LZP)zh$AYxu~8nb4-~CDF)9Q!Vm~sOGvlPX z+L_?6p@|CPlg4yD0M!=QbLnH~n5OPI^HZ*Zt94iEQh9|}_g&eSa^_zxx>A(NFS>gC z%JG!jb9Kv=Em@J$@`citWNAyPq->!im@EmZmc1eA+mP~d3*P3Ww>hiEU%%k*O8OIB zsZ6cwaf!`kn$a!RQ5N59U()QKYuNT)U$S9$%3eNu=$+$l9#7g^?l}stTCd=>L4FVY zAg>0l4V*5xHVC#XG`1%j+b`?pJb}B8y88{y3k}aE8=gf*ZPF3AZ?NHg!{vb=8;Vl) zqF3e19j{C#?c{7t1x=CHYko!dkQsmNc152_a1_%JT+u-Eiw+9nW?UuhKr9wfG?CaT zh2@2Tu~PvyeWjq!035N0eC)5HIbyFZwI0DmL<4|ep(C4XqmaD(8%1u_zG0Fx<_{Md z;6k=IleJ$kH8+DuL2wAA}?fM9P zR1MDQsBM5r3QCP9wWuZM}hT;HhUapEV* zKh+;`-?O_eZ&+~GC*Acoj?de-%o(;|t_{=ML&h8S(C5f|4F@(%LL|w$&KK!8+_e>f z)@d8mC|*+xo;nF_Xt6w1OksUW122}5ZH+ztIK%7CK%^1jImN^oSOPd*Z7+Uasyo)) zxcPWJRH`O@1FG8a?#_3gpR3&T?W$zu_PO@w z=lTxK^&OsTKa#9GGUwn(T~oF5zS(v8`Ion(3OqBr@7oJzY-Fyv$V4rjfKdPj(q!F^ zJo3#ZQuPWjZe~0U)?ONoMI}M}GH@ltf^fKq968-wPrpjO+I$!c@Z;vevMbOd{y7kZ zRnf0_X|;T0T|NERovZ!~S?yUL;TpFloou55RdHJ9$#Vl~KnLTLSbOgxET^72$s2*;Q-r(=XVz3b%X8~R`>Ev@(0 zpGFBhH56M+96B`=4M#%iHLi%L6RzIWn4Kw+bgfR0EJ;qRJ{4 zeCw0G^%wWwM~Kjp^tH_F|Il3g+SzZ!Uypz7&)y9s%Q{l-!j#>urtE`SP@XMlX!vjG z+d#lNDYDe;BX4YOgY&z9Jx7ZiUPu=cPn(0hRpF-3-r3c5fp)RtTOgmtP%73sLjH3L zr^5{t9Ynje9^9*WYpM08_%Ww|3m;=6{afqUMIB{zl9#tOF^fj>-c~P|+fTvVUP(;R z%MnjTe4!Sej^}>O$cB$8;7RlZBO?@33&_AgSa=P_35XBc$%1xK;Kl_mHYRhiliB=o zM{fq98dsmF&wdIej)OWycXB9#_cIgF$U2V&Bl$#VFg6yIrDf|Y+g_dc7{B*Lki~co zA9`u;#l0zuGhBo&St!=TP3PITDbsC&T+^EAEYaBG8nRfCr<<}xkIBX4 zR=bx>wHwL93O9MZ-DxP!tRDI2%6w+H9H&OBFW7GDvId3PP)9K&$kcE}cDi}f*LCR`?MAZ~G$1QQkZT+pxE=@V(&fx$~IU*Lz_1X91EF{`m$X3lOl3^-t z%?_VU=;0=yA^XlSp2uqnZvZ{ctTd$mPVfO^c$V0AeL#M->j(50`G=0mn!knDAWo?+ z`_%O4Fwu0@Z7)nqVJ@x!b`irva5xP>>*;yb8`wLGRO8cXl;3@227}DeQ z>68yak&0*(Bf!>>>bw$;00-=v0_W}duCkU-(2MIJj(``}P~f6%7u#OTPnvyiwmxv> zU#-7Ve~q7Q{o0uut?$;)ySC3+wv!)q#Q^M{xpL-u)$G_?^*6@eJu_dhbI!4o^zQao zb}UqFOIB_B_Q@Ytb>1s@vfS&tJ8WF-0=GTMZJ+0!owJmZ$Gg8-bk9{b%g#zSI`1}g zf9v>rt@Ewj^RB&fmc8U(_XM1X+tl#g1{FB!XfnMyFOlc)BqafMAN5o4Wl_zk_wOF2HSgisS%n+1}E_pM$NCP#X3_OIr`n|7~(ytoiDp z*Q!YWEI(5-Z>nF^Ym5!)un5Arf(N|1K0kuWU(pD^c=}8&j8#M84P|-E6Mi1VA7J74 zH6taXt*S*v)RL|t_D~BfZDyq96Y3yb)XTZst17s20Iy!wvixrDOdDigfYWN6I;VMo z8m9xA45CONqzD++VhMx`iXN_q!m=y^02>25kxn~o?Z6otfS=nt@t!3EG88S`S&k^i zP-rj`mZVT9gEgi_4mFuWp^UCjXwsQRZE02lw1q-1;=1!7`P*qKWwy_`tM8htA3Ozy zJ>$pLxAprG+Ql!Qn3X%;(8<-fGB#%ZDWIw z+8#xgdaLv{)&fW{T#CDq97lq&NHMDmlD^*&W5`y4@+(*ah@GIs9OzX+t+*n#q09{= zYC!uA*1n76dq5OxW~~Q>NF)>jYknWNsx?2WF1os1DnVckvatmvE zG)Y&mRf~X?b;`P^q#s#aY~E6>i!~)4)!NwV#VQJhu{dxNFa!86uW-r!d73SH6lb(-!*47Wwsw&M zqI#eDWgnr-1vvP>AVMd{lkR#ISWF#*amW?cy9X5m-vsfh53AY5HeW%`Yg+ z2bApt%J>0g{(!RojH>t`Tzg` delta 9206 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 BXb=DZ literal 15682 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 diff --git a/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc index ce633dde64446a92b32947613c3e45b59ef22455..bb73baa3d121f43a74802869c3c7a6e5d3359804 100644 GIT binary patch delta 2498 zcmZt|TWl29_1<~x?#%4YKJ8s&ydE$yUK_gsW8#*$X$Y2qw$PL~FxU6x7#AnR8})pr$U1FHWj$O>wJU{MtamM$?}K;!lR#6zGm zS@sNA*z*>+NanX-M!J0BwP{V6S$e#e9UK_MQK-O^_#M~*0lowOlIDnTL{SSFCQr~+ zQ0D?jaL;j-NpfJl!Qk7>Z}?p{Cv$8kjPb2i-C#4hvza2nXW>Z2Fp)l@wky>apS#rH4Y zn)vm(U!JRV^sk2dR|5UG;s0!&db+UbC9(v727!iseVU+~z&pe^xJ&548+* zSmS)jq&HEJ>o_TA897QmL+EIb5KfU{?jxuQ9{Qt;UB58z``bTYA1KeSD$n0L_;=-n zM{>(zVR`J{vHSAKHMTCv)e~->7M^;AeErmWD*i{HD{j%f3pUuy9t?vmI4s0uo3UXt zaWnApHCz@l^N02y7*e#0B3+Zq*&8M zQ&b4_m|DRi7gzQVyfAP;=~s52nza;TQaN2PD%$SMfXkKP9HCgrt18k=len;w+g!UI zaC|;QeO|$xk^)8iJ*jp4FtPo0GB!Q45dmWSfzYukblkb{VRtRP|3P|qH9cHQf2SrK zt{$mNp~d9&krgiWP0vU~jGI>t{Cnx8J(k~f3qdyY>dKqvm?<#F+JM&U4^&p=ymXu6 zZ0;!d>1hDY5~t7cb3Rh@yj%0TZKf)3)w1m)V!b3~Ah^WXY$-}BZ^Y-RfncHE>lr3o z7_^ypWh?&FET7FhTSCir3F92FS#zvuEdFmW{*vGJ6C3-!W@CWbNOWxLHea)lg``Do z**LmgLq7)=4syVvEz$*>^=_aF7mkwh8nBWcO%-i;n^7e-SZc$sa`BdSk7DigR-`+A z8tR;XwmjND6W@>*vRPH8o#i(3E6&q?1#aUDZ=H4lE8yv}yakwK$Q$9KZ^RsP1}wsT z3kNLsk)j-gB*Sg}rOHw^9bN&Hbz$tIv+|!H4B(DPFAU*hk)s_Wx}oGN6=YOUF>h%~ z!I+sb^pm6)Dh9&0BfICrj`(`MJgbe;o`Hsl7IcUrTJ277yxALEq9ea-k;FegYc-i2 zc1p9{W{PM!AX)_uTh?^92_xzUhn>k+P?W4P0f#GS$U+^I_1lydpj{asinhzt541ih zAQ8VAP5k7Cns-*8-NqUhA8u~N3r$?vF_Da_Xls^ z8?Nnru@)P-=5Oo(ky!OuU6gP1T<=+lcP;<8CiYZcskgS_zeUeZ#6U37@PS0?=J3++ zt>eq67VljHexdK$EA{lQHQ)>NEs2Zl;y7hW-r84-cRx%%x2%0QaX)#;Nu}$Foi|S{ zomw9KJkk4Qh;TPp^6^FlzZW|)|1UZHaq83ImDk4qdU&PtjhZ~M5}0@-r|ZG*ud@ub zoe{*ZHpU?cwcY7mk)B%vJfCccAf9~R|4?e9+t?#1u_EoPx9+$;_U~YHjR&Db!%xni zm^6)Vq(>$2i4+|jVn6AOQG5^*oPj6==83z5jz5=EjY2MmXa=KiQADHbNXKU?2C|wV zm2x@nG035pDGz<9nn9%*;06`ZU?3W#<{t;m$0vaoXAE_=tbG@~O1AXTFyA1k!9w!K z^iS}kbrC@E3m`UvENuCLOEdx?oT^G2J_v=y%Nu}9>w!VoxmJc846n%oY+Fx_K=G@_ j6axbBHNwzRk0h=frxIJ9m_Z_Zf$IZz{X^^@;1K(NnXWdu delta 2834 zcmb7GTWl2989ry{vO9bCu6NfMY>y2!S!^~K3?irs^<{%y6yp$}%@Q(NcE)(k?#%Yg znA+~R>r_OFR7z6ZnUnc~Q1oWJ!0gWWT<8pvJvWgo zDE|8lRfZ{jzK#xv`81;7*9*%>GXMND1H8d zbUuAS8Xg_Gu>8+9j4eO@UAq93gf`7kbq<=FXoK~TptEjOae~F$F2?e??uHH-l+Y0ipX;DU{cd31>rBNW^5U0VZaJnJ^AnolK|G?z1F@vj&-{-)O3_ zAT$-RK4JD*t4!#58*MlS+)?Y_OxHKJPqRI~XL90{knzg_3kRC5RW{}ef2E@pY>ft7 zwfVeY3R7*?XRPHSyk&41F=TUksgO5pE~ibahI1-5OOz6^*{hmXu>Qe)*c}3HJ1EJj zu4aphRLmN=Nzwz0El~Aya0Uyg8nk}T%Wa#X=+fZI$nuEw{k0d~Pu)#bx{f|-K3WMK zTlXBZhTI?TrH7oW+XF*{Sf~&@cze-Og#9d>GDpw%V&iP*U548sgvNADc^czqy@{W< z`rrR2iL>v3q1L*PEWgy(cSO{c91SY5KXoj1{FK-=AQELlA&Qz)#JsGiM&6hewFz-_ z{P5_onA21vn^$$wm{der$!W48dqpT$XAMQCbaZ2MT+G%&GbIkFbb3P}x}&ABA?Ee6 z*e_~1LoxJTF{{c@fzlDypgysSTI@Sfr%s8NQ>oMy@!KplW=qrrFxL_0{3ft1U$d=l zn4`buUoKk^4UMYLkJpn(ypb(TD|L4qDf;x)DLAQtf-wgFUQ}ibMRh!?yPQ*qk)O!t zvWAlCayXKlE#ymeb+$KOk{V;4=myD`0wfOq&gM(m+;v5^e(Dt=MOMA>%l)uC4pl!2 z%>vqqBX9Jv(Dq1ZyEU;|yj{GbRfN>S;HEFM*Q~) zjzb^;AssSq-|1!(->>KEJaY}r(@=BM)G0GEd%uw)oXHKNE3YH;Calc!^KKJx z&&ljAlUWGvf&(|mt8)>6(^;3vPEn0tE2g`l6{E$&AkD!E9WNd;nYV*R0HLuOhv_z% z|6AZi7LtOT1=B`NRzK5l$;$X6Wsm6rS0GV$x#FR&fO7x0IKp7`zq-Pr#eNnU6djym zvJFRN;rcM314%stJK1aEJ1*HL`=_E-CmW5$8f*rQ2B(;;)f4DQKVKll*5+@>3z)1N z+ymTqT)+5A{m-z`WW5(i(Dhn5fCvwFmTR7Q11;gjBfowcc!+Vp`XF=&^VVNO!=V>d z$b?dfXeE+|ShfbkU1h)Ry+rdNO^8OwVGu=9(Adm1#c=SCPyi$}Y;C5Xs1ACPc4u15 zmI!q*DcCH{5zewIsYM)p3AbU%u8ugz2?n<);*X09&BIl7_S6h zTlc*7ubs0P`QuNJi%V=p;y-s+IT(GZ(`)a&Sei#4@xFA7`KTE~o5X~N^`wV%LO16H zD9o$ZQUmI2?c5mv9{>@t&A4Vx#5F-_FDjymD1^h&!7L{~&p9Me<Uu#U zaF3Re?USVXMFWCPQ4Z%pL@#tFmYskifJ>dM_zb1C7~wIK_#>0}3!B;sCKh}(IjlU0Tewt`Jnjt(GtySi(e O5a7IbN1&^Y?f(IU_Nmwa diff --git a/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssltransport.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssltransport.cpython-312.pyc index 99439bff21b00f8eaf5db17eb059db048e9820ef..4a07668e6514eeb35b4b078034f94f7d74bd8ad9 100644 GIT binary patch delta 3939 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 literal 13334 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 diff --git a/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/url.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/url.cpython-312.pyc index dcfa636a5d8b5ca1ed4c383e6866914ae99b95d3..cfa22cb11b6bacc56bb59fb6cd4d54aa07f030fe 100644 GIT binary patch literal 15775 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 literal 16236 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 diff --git a/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/wait.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/wait.cpython-312.pyc index c00406e54db7d89c142ef8465cbe464e77a3b4dd..2f749e3c7e54e59e5417511e53de83e25b8e77a0 100644 GIT binary patch literal 4407 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 delta 1897 zcmaJ>-ES0C6u);A>7^hoiq1* z+Um9$XVMxCh|Gk3ajjdC%OnQL;lHYp8`)5)1{1QW z24G6E1sW(8%ui{jIFQ~?KNO?s!t^#U6jZhy#7cHJ2wk49=Zj(0hpK&EW7W&;K_%j6 zd2J%_&sM6uKM!#3T)|*zh+!FkSU)v*^5h4_*g!e>@kgv1XHDjhK&a1H7G5C$_s?<& zEeNduJ+V6OMq_LTPV@jo20&hpw^^t7H{I3V2QzP>DFk@B+RA?G)cz-WO3N=>(An$8 zO=F=ozmVBEukK`6R2iPNfk!U8dRaJ`eFJrJ0UPqa;nCGjQgTUonea6Ra$WqG**&ew zYLnt}?{Lm4=WD!4E@SYFWLbeGWnlC3tP?Qa?2Njf`_5#@#wyhjXCe#&wg(KS;fam{ z%#enuYi$pC`D+V*tWAR@}X)ceY-7%l(iLfxii)$4%t2L z;@L_b13{j1b`gf-Ny#!@W-IG-T7(|T zW(}bIdG2;>InHp6PuH05I1HKCyhh2oaeBB`EpzxTMaj3ya2z)Xz!kqK46j3SO0wg! z9az&Gk-V3Ov0@$_54~F8A7_Wagk+5V0BEErr4LE(V`BeKrtODh`=d9v%mK2Q1GOXvYDo&vmUpT2*pr+}kIP2< E2bt5B@Bjb+ diff --git a/venv/lib/python3.12/site-packages/urllib3/util/connection.py b/venv/lib/python3.12/site-packages/urllib3/util/connection.py index f92519e..6af1138 100644 --- a/venv/lib/python3.12/site-packages/urllib3/util/connection.py +++ b/venv/lib/python3.12/site-packages/urllib3/util/connection.py @@ -1,23 +1,33 @@ -from __future__ import annotations +from __future__ import absolute_import import socket -import typing +from ..contrib import _appengine_environ from ..exceptions import LocationParseError -from .timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT - -_TYPE_SOCKET_OPTIONS = list[tuple[int, int, typing.Union[int, bytes]]] - -if typing.TYPE_CHECKING: - from .._base_connection import BaseHTTPConnection +from ..packages import six +from .wait import NoWayToWaitForSocketError, wait_for_read -def is_connection_dropped(conn: BaseHTTPConnection) -> bool: # Platform-specific +def is_connection_dropped(conn): # Platform-specific """ Returns True if the connection is dropped and should be closed. - :param conn: :class:`urllib3.connection.HTTPConnection` object. + + :param conn: + :class:`http.client.HTTPConnection` object. + + Note: For platforms like AppEngine, this will always return ``False`` to + let the platform handle connection recycling transparently for us. """ - return not conn.is_connected + sock = getattr(conn, "sock", False) + if sock is False: # Platform-specific: AppEngine + return False + if sock is None: # Connection already closed (such as by httplib). + return True + try: + # Returns True if readable, which here means it's been dropped + return wait_for_read(sock, timeout=0.0) + except NoWayToWaitForSocketError: # Platform-specific: AppEngine + return False # This function is copied from socket.py in the Python 2.7 standard @@ -25,11 +35,11 @@ def is_connection_dropped(conn: BaseHTTPConnection) -> bool: # Platform-specifi # One additional modification is that we avoid binding to IPv6 servers # discovered in DNS if the system doesn't have IPv6 functionality. def create_connection( - address: tuple[str, int], - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - socket_options: _TYPE_SOCKET_OPTIONS | None = None, -) -> socket.socket: + address, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, + socket_options=None, +): """Connect to *address* and return the socket object. Convenience function. Connect to *address* (a 2-tuple ``(host, @@ -55,7 +65,9 @@ def create_connection( try: host.encode("idna") except UnicodeError: - raise LocationParseError(f"'{host}', label empty or too long") from None + return six.raise_from( + LocationParseError(u"'%s', label empty or too long" % host), None + ) for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res @@ -66,33 +78,26 @@ def create_connection( # If provided, set socket level options before connecting. _set_socket_options(sock, socket_options) - if timeout is not _DEFAULT_TIMEOUT: + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: sock.settimeout(timeout) if source_address: sock.bind(source_address) sock.connect(sa) - # Break explicitly a reference cycle - err = None return sock - except OSError as _: - err = _ + except socket.error as e: + err = e if sock is not None: sock.close() + sock = None if err is not None: - try: - raise err - finally: - # Break explicitly a reference cycle - err = None - else: - raise OSError("getaddrinfo returns an empty list") + raise err + + raise socket.error("getaddrinfo returns an empty list") -def _set_socket_options( - sock: socket.socket, options: _TYPE_SOCKET_OPTIONS | None -) -> None: +def _set_socket_options(sock, options): if options is None: return @@ -100,7 +105,7 @@ def _set_socket_options( sock.setsockopt(*opt) -def allowed_gai_family() -> socket.AddressFamily: +def allowed_gai_family(): """This function is designed to work in the context of getaddrinfo, where family=socket.AF_UNSPEC is the default and will perform a DNS search for both IPv6 and IPv4 records.""" @@ -111,11 +116,18 @@ def allowed_gai_family() -> socket.AddressFamily: return family -def _has_ipv6(host: str) -> bool: +def _has_ipv6(host): """Returns True if the system can bind an IPv6 address.""" sock = None has_ipv6 = False + # App Engine doesn't support IPV6 sockets and actually has a quota on the + # number of sockets that can be used, so just early out here instead of + # creating a socket needlessly. + # See https://github.com/urllib3/urllib3/issues/1446 + if _appengine_environ.is_appengine_sandbox(): + return False + if socket.has_ipv6: # has_ipv6 returns true if cPython was compiled with IPv6 support. # It does not tell us if the system has IPv6 support enabled. To diff --git a/venv/lib/python3.12/site-packages/urllib3/util/proxy.py b/venv/lib/python3.12/site-packages/urllib3/util/proxy.py index 908fc66..2199cc7 100644 --- a/venv/lib/python3.12/site-packages/urllib3/util/proxy.py +++ b/venv/lib/python3.12/site-packages/urllib3/util/proxy.py @@ -1,18 +1,9 @@ -from __future__ import annotations - -import typing - -from .url import Url - -if typing.TYPE_CHECKING: - from ..connection import ProxyConfig +from .ssl_ import create_urllib3_context, resolve_cert_reqs, resolve_ssl_version def connection_requires_http_tunnel( - proxy_url: Url | None = None, - proxy_config: ProxyConfig | None = None, - destination_scheme: str | None = None, -) -> bool: + proxy_url=None, proxy_config=None, destination_scheme=None +): """ Returns True if the connection requires an HTTP CONNECT through the proxy. @@ -41,3 +32,26 @@ def connection_requires_http_tunnel( # Otherwise always use a tunnel. return True + + +def create_proxy_ssl_context( + ssl_version, cert_reqs, ca_certs=None, ca_cert_dir=None, ca_cert_data=None +): + """ + Generates a default proxy ssl context if one hasn't been provided by the + user. + """ + ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(ssl_version), + cert_reqs=resolve_cert_reqs(cert_reqs), + ) + + if ( + not ca_certs + and not ca_cert_dir + and not ca_cert_data + and hasattr(ssl_context, "load_default_certs") + ): + ssl_context.load_default_certs() + + return ssl_context diff --git a/venv/lib/python3.12/site-packages/urllib3/util/request.py b/venv/lib/python3.12/site-packages/urllib3/util/request.py index 6c2372b..b574b08 100644 --- a/venv/lib/python3.12/site-packages/urllib3/util/request.py +++ b/venv/lib/python3.12/site-packages/urllib3/util/request.py @@ -1,16 +1,9 @@ -from __future__ import annotations +from __future__ import absolute_import -import io -import sys -import typing from base64 import b64encode -from enum import Enum from ..exceptions import UnrewindableBodyError -from .util import to_bytes - -if typing.TYPE_CHECKING: - from typing import Final +from ..packages.six import b, integer_types # Pass as a value within ``headers`` to skip # emitting some HTTP headers that are added automatically. @@ -22,49 +15,25 @@ SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"]) ACCEPT_ENCODING = "gzip,deflate" try: try: - import brotlicffi as _unused_module_brotli # type: ignore[import-not-found] # noqa: F401 + import brotlicffi as _unused_module_brotli # noqa: F401 except ImportError: - import brotli as _unused_module_brotli # type: ignore[import-not-found] # noqa: F401 + import brotli as _unused_module_brotli # noqa: F401 except ImportError: pass else: ACCEPT_ENCODING += ",br" -try: - if sys.version_info >= (3, 14): - from compression import zstd as _unused_module_zstd # noqa: F401 - else: - from backports import zstd as _unused_module_zstd # noqa: F401 -except ImportError: - pass -else: - ACCEPT_ENCODING += ",zstd" - - -class _TYPE_FAILEDTELL(Enum): - token = 0 - - -_FAILEDTELL: Final[_TYPE_FAILEDTELL] = _TYPE_FAILEDTELL.token - -_TYPE_BODY_POSITION = typing.Union[int, _TYPE_FAILEDTELL] - -# When sending a request with these methods we aren't expecting -# a body so don't need to set an explicit 'Content-Length: 0' -# The reason we do this in the negative instead of tracking methods -# which 'should' have a body is because unknown methods should be -# treated as if they were 'POST' which *does* expect a body. -_METHODS_NOT_EXPECTING_BODY = {"GET", "HEAD", "DELETE", "TRACE", "OPTIONS", "CONNECT"} +_FAILEDTELL = object() def make_headers( - keep_alive: bool | None = None, - accept_encoding: bool | list[str] | str | None = None, - user_agent: str | None = None, - basic_auth: str | None = None, - proxy_basic_auth: str | None = None, - disable_cache: bool | None = None, -) -> dict[str, str]: + keep_alive=None, + accept_encoding=None, + user_agent=None, + basic_auth=None, + proxy_basic_auth=None, + disable_cache=None, +): """ Shortcuts for generating request headers. @@ -73,11 +42,7 @@ def make_headers( :param accept_encoding: Can be a boolean, list, or string. - ``True`` translates to 'gzip,deflate'. If the dependencies for - Brotli (either the ``brotli`` or ``brotlicffi`` package) and/or - Zstandard (the ``backports.zstd`` package for Python before 3.14) - algorithms are installed, then their encodings are - included in the string ('br' and 'zstd', respectively). + ``True`` translates to 'gzip,deflate'. List will get joined by comma. String will be used as provided. @@ -96,18 +61,14 @@ def make_headers( :param disable_cache: If ``True``, adds 'cache-control: no-cache' header. - Example: + Example:: - .. code-block:: python - - import urllib3 - - print(urllib3.util.make_headers(keep_alive=True, user_agent="Batman/1.0")) - # {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} - print(urllib3.util.make_headers(accept_encoding=True)) - # {'accept-encoding': 'gzip,deflate'} + >>> make_headers(keep_alive=True, user_agent="Batman/1.0") + {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + >>> make_headers(accept_encoding=True) + {'accept-encoding': 'gzip,deflate'} """ - headers: dict[str, str] = {} + headers = {} if accept_encoding: if isinstance(accept_encoding, str): pass @@ -124,14 +85,12 @@ def make_headers( headers["connection"] = "keep-alive" if basic_auth: - headers["authorization"] = ( - f"Basic {b64encode(basic_auth.encode('latin-1')).decode()}" - ) + headers["authorization"] = "Basic " + b64encode(b(basic_auth)).decode("utf-8") if proxy_basic_auth: - headers["proxy-authorization"] = ( - f"Basic {b64encode(proxy_basic_auth.encode('latin-1')).decode()}" - ) + headers["proxy-authorization"] = "Basic " + b64encode( + b(proxy_basic_auth) + ).decode("utf-8") if disable_cache: headers["cache-control"] = "no-cache" @@ -139,9 +98,7 @@ def make_headers( return headers -def set_file_position( - body: typing.Any, pos: _TYPE_BODY_POSITION | None -) -> _TYPE_BODY_POSITION | None: +def set_file_position(body, pos): """ If a position is provided, move file to that point. Otherwise, we'll attempt to record a position for future use. @@ -151,7 +108,7 @@ def set_file_position( elif getattr(body, "tell", None) is not None: try: pos = body.tell() - except OSError: + except (IOError, OSError): # This differentiates from None, allowing us to catch # a failed `tell()` later when trying to rewind the body. pos = _FAILEDTELL @@ -159,7 +116,7 @@ def set_file_position( return pos -def rewind_body(body: typing.IO[typing.AnyStr], body_pos: _TYPE_BODY_POSITION) -> None: +def rewind_body(body, body_pos): """ Attempt to rewind body to a certain position. Primarily used for request redirects and retries. @@ -171,13 +128,13 @@ def rewind_body(body: typing.IO[typing.AnyStr], body_pos: _TYPE_BODY_POSITION) - Position to seek to in file. """ body_seek = getattr(body, "seek", None) - if body_seek is not None and isinstance(body_pos, int): + if body_seek is not None and isinstance(body_pos, integer_types): try: body_seek(body_pos) - except OSError as e: + except (IOError, OSError): raise UnrewindableBodyError( "An error occurred when rewinding request body for redirect/retry." - ) from e + ) elif body_pos is _FAILEDTELL: raise UnrewindableBodyError( "Unable to record file position for rewinding " @@ -185,79 +142,5 @@ def rewind_body(body: typing.IO[typing.AnyStr], body_pos: _TYPE_BODY_POSITION) - ) else: raise ValueError( - f"body_pos must be of type integer, instead it was {type(body_pos)}." + "body_pos must be of type integer, instead it was %s." % type(body_pos) ) - - -class ChunksAndContentLength(typing.NamedTuple): - chunks: typing.Iterable[bytes] | None - content_length: int | None - - -def body_to_chunks( - body: typing.Any | None, method: str, blocksize: int -) -> ChunksAndContentLength: - """Takes the HTTP request method, body, and blocksize and - transforms them into an iterable of chunks to pass to - socket.sendall() and an optional 'Content-Length' header. - - A 'Content-Length' of 'None' indicates the length of the body - can't be determined so should use 'Transfer-Encoding: chunked' - for framing instead. - """ - - chunks: typing.Iterable[bytes] | None - content_length: int | None - - # No body, we need to make a recommendation on 'Content-Length' - # based on whether that request method is expected to have - # a body or not. - if body is None: - chunks = None - if method.upper() not in _METHODS_NOT_EXPECTING_BODY: - content_length = 0 - else: - content_length = None - - # Bytes or strings become bytes - elif isinstance(body, (str, bytes)): - chunks = (to_bytes(body),) - content_length = len(chunks[0]) - - # File-like object, TODO: use seek() and tell() for length? - elif hasattr(body, "read"): - - def chunk_readable() -> typing.Iterable[bytes]: - encode = isinstance(body, io.TextIOBase) - while True: - datablock = body.read(blocksize) - if not datablock: - break - if encode: - datablock = datablock.encode("utf-8") - yield datablock - - chunks = chunk_readable() - content_length = None - - # Otherwise we need to start checking via duck-typing. - else: - try: - # Check if the body implements the buffer API. - mv = memoryview(body) - except TypeError: - try: - # Check if the body is an iterable - chunks = iter(body) - content_length = None - except TypeError: - raise TypeError( - f"'body' must be a bytes-like object, file-like " - f"object, or iterable. Instead was {body!r}" - ) from None - else: - # Since it implements the buffer API can be passed directly to socket.sendall() - chunks = (body,) - content_length = mv.nbytes - - return ChunksAndContentLength(chunks=chunks, content_length=content_length) diff --git a/venv/lib/python3.12/site-packages/urllib3/util/response.py b/venv/lib/python3.12/site-packages/urllib3/util/response.py index 0f45786..5ea609c 100644 --- a/venv/lib/python3.12/site-packages/urllib3/util/response.py +++ b/venv/lib/python3.12/site-packages/urllib3/util/response.py @@ -1,12 +1,12 @@ -from __future__ import annotations +from __future__ import absolute_import -import http.client as httplib from email.errors import MultipartInvariantViolationDefect, StartBoundaryNotFoundDefect from ..exceptions import HeaderParsingError +from ..packages.six.moves import http_client as httplib -def is_fp_closed(obj: object) -> bool: +def is_fp_closed(obj): """ Checks whether a given file-like object is closed. @@ -17,27 +17,27 @@ def is_fp_closed(obj: object) -> bool: try: # Check `isclosed()` first, in case Python3 doesn't set `closed`. # GH Issue #928 - return obj.isclosed() # type: ignore[no-any-return, attr-defined] + return obj.isclosed() except AttributeError: pass try: # Check via the official file-like-object way. - return obj.closed # type: ignore[no-any-return, attr-defined] + return obj.closed except AttributeError: pass try: # Check if the object is a container for another file-like object that # gets released on exhaustion (e.g. HTTPResponse). - return obj.fp is None # type: ignore[attr-defined] + return obj.fp is None except AttributeError: pass raise ValueError("Unable to determine whether fp is closed.") -def assert_header_parsing(headers: httplib.HTTPMessage) -> None: +def assert_header_parsing(headers): """ Asserts whether all headers have been successfully parsed. Extracts encountered errors from the result of parsing headers. @@ -53,49 +53,55 @@ def assert_header_parsing(headers: httplib.HTTPMessage) -> None: # This will fail silently if we pass in the wrong kind of parameter. # To make debugging easier add an explicit check. if not isinstance(headers, httplib.HTTPMessage): - raise TypeError(f"expected httplib.Message, got {type(headers)}.") + raise TypeError("expected httplib.Message, got {0}.".format(type(headers))) + + defects = getattr(headers, "defects", None) + get_payload = getattr(headers, "get_payload", None) unparsed_data = None + if get_payload: + # get_payload is actually email.message.Message.get_payload; + # we're only interested in the result if it's not a multipart message + if not headers.is_multipart(): + payload = get_payload() - # get_payload is actually email.message.Message.get_payload; - # we're only interested in the result if it's not a multipart message - if not headers.is_multipart(): - payload = headers.get_payload() + if isinstance(payload, (bytes, str)): + unparsed_data = payload + if defects: + # httplib is assuming a response body is available + # when parsing headers even when httplib only sends + # header data to parse_headers() This results in + # defects on multipart responses in particular. + # See: https://github.com/urllib3/urllib3/issues/800 - if isinstance(payload, (bytes, str)): - unparsed_data = payload - - # httplib is assuming a response body is available - # when parsing headers even when httplib only sends - # header data to parse_headers() This results in - # defects on multipart responses in particular. - # See: https://github.com/urllib3/urllib3/issues/800 - - # So we ignore the following defects: - # - StartBoundaryNotFoundDefect: - # The claimed start boundary was never found. - # - MultipartInvariantViolationDefect: - # A message claimed to be a multipart but no subparts were found. - defects = [ - defect - for defect in headers.defects - if not isinstance( - defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) - ) - ] + # So we ignore the following defects: + # - StartBoundaryNotFoundDefect: + # The claimed start boundary was never found. + # - MultipartInvariantViolationDefect: + # A message claimed to be a multipart but no subparts were found. + defects = [ + defect + for defect in defects + if not isinstance( + defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) + ) + ] if defects or unparsed_data: raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) -def is_response_to_head(response: httplib.HTTPResponse) -> bool: +def is_response_to_head(response): """ Checks whether the request of a response has been a HEAD-request. + Handles the quirks of AppEngine. :param http.client.HTTPResponse response: Response to check if the originating request used 'HEAD' as a method. """ # FIXME: Can we do this somehow without accessing private httplib _method? - method_str = response._method # type: str # type: ignore[attr-defined] - return method_str.upper() == "HEAD" + method = response._method + if isinstance(method, int): # Platform-specific: Appengine + return method == 3 + return method.upper() == "HEAD" diff --git a/venv/lib/python3.12/site-packages/urllib3/util/retry.py b/venv/lib/python3.12/site-packages/urllib3/util/retry.py index 7649898..9a1e90d 100644 --- a/venv/lib/python3.12/site-packages/urllib3/util/retry.py +++ b/venv/lib/python3.12/site-packages/urllib3/util/retry.py @@ -1,13 +1,12 @@ -from __future__ import annotations +from __future__ import absolute_import import email import logging -import random import re import time -import typing +import warnings +from collections import namedtuple from itertools import takewhile -from types import TracebackType from ..exceptions import ( ConnectTimeoutError, @@ -18,51 +17,97 @@ from ..exceptions import ( ReadTimeoutError, ResponseError, ) -from .util import reraise - -if typing.TYPE_CHECKING: - from typing_extensions import Self - - from ..connectionpool import ConnectionPool - from ..response import BaseHTTPResponse +from ..packages import six log = logging.getLogger(__name__) # Data structure for representing the metadata of requests that result in a retry. -class RequestHistory(typing.NamedTuple): - method: str | None - url: str | None - error: Exception | None - status: int | None - redirect_location: str | None +RequestHistory = namedtuple( + "RequestHistory", ["method", "url", "error", "status", "redirect_location"] +) -class Retry: +# TODO: In v2 we can remove this sentinel and metaclass with deprecated options. +_Default = object() + + +class _RetryMeta(type): + @property + def DEFAULT_METHOD_WHITELIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + return cls.DEFAULT_ALLOWED_METHODS + + @DEFAULT_METHOD_WHITELIST.setter + def DEFAULT_METHOD_WHITELIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + cls.DEFAULT_ALLOWED_METHODS = value + + @property + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + return cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT + + @DEFAULT_REDIRECT_HEADERS_BLACKLIST.setter + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT = value + + @property + def BACKOFF_MAX(cls): + warnings.warn( + "Using 'Retry.BACKOFF_MAX' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead", + DeprecationWarning, + ) + return cls.DEFAULT_BACKOFF_MAX + + @BACKOFF_MAX.setter + def BACKOFF_MAX(cls, value): + warnings.warn( + "Using 'Retry.BACKOFF_MAX' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead", + DeprecationWarning, + ) + cls.DEFAULT_BACKOFF_MAX = value + + +@six.add_metaclass(_RetryMeta) +class Retry(object): """Retry configuration. Each retry attempt will create a new Retry object with updated values, so they can be safely reused. - Retries can be defined as a default for a pool: - - .. code-block:: python + Retries can be defined as a default for a pool:: retries = Retry(connect=5, read=2, redirect=5) http = PoolManager(retries=retries) - response = http.request("GET", "https://example.com/") + response = http.request('GET', 'http://example.com/') - Or per-request (which overrides the default for the pool): + Or per-request (which overrides the default for the pool):: - .. code-block:: python + response = http.request('GET', 'http://example.com/', retries=Retry(10)) - response = http.request("GET", "https://example.com/", retries=Retry(10)) + Retries can be disabled by passing ``False``:: - Retries can be disabled by passing ``False``: - - .. code-block:: python - - response = http.request("GET", "https://example.com/", retries=False) + response = http.request('GET', 'http://example.com/', retries=False) Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless retries are disabled, in which case the causing exception will be raised. @@ -124,16 +169,21 @@ class Retry: If ``total`` is not set, it's a good idea to set this to 0 to account for unexpected edge cases and avoid infinite retry loops. - :param Collection allowed_methods: + :param iterable allowed_methods: Set of uppercased HTTP method verbs that we should retry on. By default, we only retry on methods which are considered to be idempotent (multiple requests with the same parameters end with the same state). See :attr:`Retry.DEFAULT_ALLOWED_METHODS`. - Set to a ``None`` value to retry on any verb. + Set to a ``False`` value to retry on any verb. - :param Collection status_forcelist: + .. warning:: + + Previously this parameter was named ``method_whitelist``, that + usage is deprecated in v1.26.0 and will be removed in v2.0. + + :param iterable status_forcelist: A set of integer HTTP status codes that we should force a retry on. A retry is initiated if the request method is in ``allowed_methods`` and the response status code is in ``status_forcelist``. @@ -145,25 +195,13 @@ class Retry: (most errors are resolved immediately by a second try without a delay). urllib3 will sleep for:: - {backoff factor} * (2 ** ({number of previous retries})) + {backoff factor} * (2 ** ({number of total retries} - 1)) - seconds. If `backoff_jitter` is non-zero, this sleep is extended by:: + seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep + for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer + than :attr:`Retry.DEFAULT_BACKOFF_MAX`. - random.uniform(0, {backoff jitter}) - - seconds. For example, if the backoff_factor is 0.1, then :func:`Retry.sleep` will - sleep for [0.0s, 0.2s, 0.4s, 0.8s, ...] between retries. No backoff will ever - be longer than `backoff_max`. - - By default, backoff is disabled (factor set to 0). - - :param float backoff_max: - The maximum backoff time (in seconds) between retry attempts. - This value caps the computed backoff from `backoff_factor`. - - :param float backoff_jitter: - Random jitter amount (in seconds) added to the computed backoff. - Jitter is sampled uniformly from `0` to `backoff_jitter`. + By default, backoff is disabled (set to 0). :param bool raise_on_redirect: Whether, if the number of redirects is exhausted, to raise a MaxRetryError, or to return a response with a @@ -182,15 +220,10 @@ class Retry: Whether to respect Retry-After header on status codes defined as :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. - :param Collection remove_headers_on_redirect: + :param iterable remove_headers_on_redirect: Sequence of headers to remove from the request when a response indicating a redirect is returned before firing off the redirected request. - - :param int retry_after_max: Number of seconds to allow as the maximum for - Retry-After headers. Defaults to :attr:`Retry.DEFAULT_RETRY_AFTER_MAX`. - Any Retry-After headers larger than this value will be limited to this - value. """ #: Default methods to be used for ``allowed_methods`` @@ -206,38 +239,48 @@ class Retry: ["Cookie", "Authorization", "Proxy-Authorization"] ) - #: Default maximum backoff time. + #: Maximum backoff time. DEFAULT_BACKOFF_MAX = 120 - # This is undocumented in the RFC. Setting to 6 hours matches other popular libraries. - #: Default maximum allowed value for Retry-After headers in seconds - DEFAULT_RETRY_AFTER_MAX: typing.Final[int] = 21600 - - # Backward compatibility; assigned outside of the class. - DEFAULT: typing.ClassVar[Retry] - def __init__( self, - total: bool | int | None = 10, - connect: int | None = None, - read: int | None = None, - redirect: bool | int | None = None, - status: int | None = None, - other: int | None = None, - allowed_methods: typing.Collection[str] | None = DEFAULT_ALLOWED_METHODS, - status_forcelist: typing.Collection[int] | None = None, - backoff_factor: float = 0, - backoff_max: float = DEFAULT_BACKOFF_MAX, - raise_on_redirect: bool = True, - raise_on_status: bool = True, - history: tuple[RequestHistory, ...] | None = None, - respect_retry_after_header: bool = True, - remove_headers_on_redirect: typing.Collection[ - str - ] = DEFAULT_REMOVE_HEADERS_ON_REDIRECT, - backoff_jitter: float = 0.0, - retry_after_max: int = DEFAULT_RETRY_AFTER_MAX, - ) -> None: + total=10, + connect=None, + read=None, + redirect=None, + status=None, + other=None, + allowed_methods=_Default, + status_forcelist=None, + backoff_factor=0, + raise_on_redirect=True, + raise_on_status=True, + history=None, + respect_retry_after_header=True, + remove_headers_on_redirect=_Default, + # TODO: Deprecated, remove in v2.0 + method_whitelist=_Default, + ): + + if method_whitelist is not _Default: + if allowed_methods is not _Default: + raise ValueError( + "Using both 'allowed_methods' and " + "'method_whitelist' together is not allowed. " + "Instead only use 'allowed_methods'" + ) + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + stacklevel=2, + ) + allowed_methods = method_whitelist + if allowed_methods is _Default: + allowed_methods = self.DEFAULT_ALLOWED_METHODS + if remove_headers_on_redirect is _Default: + remove_headers_on_redirect = self.DEFAULT_REMOVE_HEADERS_ON_REDIRECT + self.total = total self.connect = connect self.read = read @@ -252,18 +295,15 @@ class Retry: self.status_forcelist = status_forcelist or set() self.allowed_methods = allowed_methods self.backoff_factor = backoff_factor - self.backoff_max = backoff_max - self.retry_after_max = retry_after_max self.raise_on_redirect = raise_on_redirect self.raise_on_status = raise_on_status - self.history = history or () + self.history = history or tuple() self.respect_retry_after_header = respect_retry_after_header self.remove_headers_on_redirect = frozenset( - h.lower() for h in remove_headers_on_redirect + [h.lower() for h in remove_headers_on_redirect] ) - self.backoff_jitter = backoff_jitter - def new(self, **kw: typing.Any) -> Self: + def new(self, **kw): params = dict( total=self.total, connect=self.connect, @@ -271,29 +311,36 @@ class Retry: redirect=self.redirect, status=self.status, other=self.other, - allowed_methods=self.allowed_methods, status_forcelist=self.status_forcelist, backoff_factor=self.backoff_factor, - backoff_max=self.backoff_max, - retry_after_max=self.retry_after_max, raise_on_redirect=self.raise_on_redirect, raise_on_status=self.raise_on_status, history=self.history, remove_headers_on_redirect=self.remove_headers_on_redirect, respect_retry_after_header=self.respect_retry_after_header, - backoff_jitter=self.backoff_jitter, ) + # TODO: If already given in **kw we use what's given to us + # If not given we need to figure out what to pass. We decide + # based on whether our class has the 'method_whitelist' property + # and if so we pass the deprecated 'method_whitelist' otherwise + # we use 'allowed_methods'. Remove in v2.0 + if "method_whitelist" not in kw and "allowed_methods" not in kw: + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + params["method_whitelist"] = self.allowed_methods + else: + params["allowed_methods"] = self.allowed_methods + params.update(kw) - return type(self)(**params) # type: ignore[arg-type] + return type(self)(**params) @classmethod - def from_int( - cls, - retries: Retry | bool | int | None, - redirect: bool | int | None = True, - default: Retry | bool | int | None = None, - ) -> Retry: + def from_int(cls, retries, redirect=True, default=None): """Backwards-compatibility for the old retries format.""" if retries is None: retries = default if default is not None else cls.DEFAULT @@ -306,7 +353,7 @@ class Retry: log.debug("Converted retries value: %r -> %r", retries, new_retries) return new_retries - def get_backoff_time(self) -> float: + def get_backoff_time(self): """Formula for computing the current backoff :rtype: float @@ -321,32 +368,32 @@ class Retry: return 0 backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) - if self.backoff_jitter != 0.0: - backoff_value += random.random() * self.backoff_jitter - return float(max(0, min(self.backoff_max, backoff_value))) + return min(self.DEFAULT_BACKOFF_MAX, backoff_value) - def parse_retry_after(self, retry_after: str) -> float: - seconds: float + def parse_retry_after(self, retry_after): # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 if re.match(r"^\s*[0-9]+\s*$", retry_after): seconds = int(retry_after) else: retry_date_tuple = email.utils.parsedate_tz(retry_after) if retry_date_tuple is None: - raise InvalidHeader(f"Invalid Retry-After header: {retry_after}") + raise InvalidHeader("Invalid Retry-After header: %s" % retry_after) + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] retry_date = email.utils.mktime_tz(retry_date_tuple) seconds = retry_date - time.time() - seconds = max(seconds, 0) - - # Check the seconds do not exceed the specified maximum - if seconds > self.retry_after_max: - seconds = self.retry_after_max + if seconds < 0: + seconds = 0 return seconds - def get_retry_after(self, response: BaseHTTPResponse) -> float | None: + def get_retry_after(self, response): """Get the value of Retry-After in seconds.""" retry_after = response.headers.get("Retry-After") @@ -356,7 +403,7 @@ class Retry: return self.parse_retry_after(retry_after) - def sleep_for_retry(self, response: BaseHTTPResponse) -> bool: + def sleep_for_retry(self, response=None): retry_after = self.get_retry_after(response) if retry_after: time.sleep(retry_after) @@ -364,13 +411,13 @@ class Retry: return False - def _sleep_backoff(self) -> None: + def _sleep_backoff(self): backoff = self.get_backoff_time() if backoff <= 0: return time.sleep(backoff) - def sleep(self, response: BaseHTTPResponse | None = None) -> None: + def sleep(self, response=None): """Sleep between retry attempts. This method will respect a server's ``Retry-After`` response header @@ -386,7 +433,7 @@ class Retry: self._sleep_backoff() - def _is_connection_error(self, err: Exception) -> bool: + def _is_connection_error(self, err): """Errors when we're fairly sure that the server did not receive the request, so it should be safe to retry. """ @@ -394,23 +441,33 @@ class Retry: err = err.original_error return isinstance(err, ConnectTimeoutError) - def _is_read_error(self, err: Exception) -> bool: + def _is_read_error(self, err): """Errors that occur after the request has been started, so we should assume that the server began processing it. """ return isinstance(err, (ReadTimeoutError, ProtocolError)) - def _is_method_retryable(self, method: str) -> bool: + def _is_method_retryable(self, method): """Checks if a given HTTP method should be retried upon, depending if it is included in the allowed_methods """ - if self.allowed_methods and method.upper() not in self.allowed_methods: + # TODO: For now favor if the Retry implementation sets its own method_whitelist + # property outside of our constructor to avoid breaking custom implementations. + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + allowed_methods = self.method_whitelist + else: + allowed_methods = self.allowed_methods + + if allowed_methods and method.upper() not in allowed_methods: return False return True - def is_retry( - self, method: str, status_code: int, has_retry_after: bool = False - ) -> bool: + def is_retry(self, method, status_code, has_retry_after=False): """Is this method/status code retryable? (Based on allowlists and control variables such as the number of total retries to allow, whether to respect the Retry-After header, whether this header is present, and @@ -423,27 +480,24 @@ class Retry: if self.status_forcelist and status_code in self.status_forcelist: return True - return bool( + return ( self.total and self.respect_retry_after_header and has_retry_after and (status_code in self.RETRY_AFTER_STATUS_CODES) ) - def is_exhausted(self) -> bool: + def is_exhausted(self): """Are we out of retries?""" - retry_counts = [ - x - for x in ( - self.total, - self.connect, - self.read, - self.redirect, - self.status, - self.other, - ) - if x - ] + retry_counts = ( + self.total, + self.connect, + self.read, + self.redirect, + self.status, + self.other, + ) + retry_counts = list(filter(None, retry_counts)) if not retry_counts: return False @@ -451,18 +505,18 @@ class Retry: def increment( self, - method: str | None = None, - url: str | None = None, - response: BaseHTTPResponse | None = None, - error: Exception | None = None, - _pool: ConnectionPool | None = None, - _stacktrace: TracebackType | None = None, - ) -> Self: + method=None, + url=None, + response=None, + error=None, + _pool=None, + _stacktrace=None, + ): """Return a new Retry object with incremented retry counters. :param response: A response object, or None, if the server did not return a response. - :type response: :class:`~urllib3.response.BaseHTTPResponse` + :type response: :class:`~urllib3.response.HTTPResponse` :param Exception error: An error encountered during the request, or None if the response was received successfully. @@ -470,7 +524,7 @@ class Retry: """ if self.total is False and error: # Disabled, indicate to re-raise the error. - raise reraise(type(error), error, _stacktrace) + raise six.reraise(type(error), error, _stacktrace) total = self.total if total is not None: @@ -488,14 +542,14 @@ class Retry: if error and self._is_connection_error(error): # Connect retry? if connect is False: - raise reraise(type(error), error, _stacktrace) + raise six.reraise(type(error), error, _stacktrace) elif connect is not None: connect -= 1 elif error and self._is_read_error(error): # Read retry? - if read is False or method is None or not self._is_method_retryable(method): - raise reraise(type(error), error, _stacktrace) + if read is False or not self._is_method_retryable(method): + raise six.reraise(type(error), error, _stacktrace) elif read is not None: read -= 1 @@ -509,9 +563,7 @@ class Retry: if redirect is not None: redirect -= 1 cause = "too many redirects" - response_redirect_location = response.get_redirect_location() - if response_redirect_location: - redirect_location = response_redirect_location + redirect_location = response.get_redirect_location() status = response.status else: @@ -539,18 +591,31 @@ class Retry: ) if new_retry.is_exhausted(): - reason = error or ResponseError(cause) - raise MaxRetryError(_pool, url, reason) from reason # type: ignore[arg-type] + raise MaxRetryError(_pool, url, error or ResponseError(cause)) log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) return new_retry - def __repr__(self) -> str: + def __repr__(self): return ( - f"{type(self).__name__}(total={self.total}, connect={self.connect}, " - f"read={self.read}, redirect={self.redirect}, status={self.status})" - ) + "{cls.__name__}(total={self.total}, connect={self.connect}, " + "read={self.read}, redirect={self.redirect}, status={self.status})" + ).format(cls=type(self), self=self) + + def __getattr__(self, item): + if item == "method_whitelist": + # TODO: Remove this deprecated alias in v2.0 + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + return self.allowed_methods + try: + return getattr(super(Retry, self), item) + except AttributeError: + return getattr(Retry, item) # For backwards compatibility (equivalent to pre-v1.9): diff --git a/venv/lib/python3.12/site-packages/urllib3/util/ssl_.py b/venv/lib/python3.12/site-packages/urllib3/util/ssl_.py index e66549a..f26809c 100644 --- a/venv/lib/python3.12/site-packages/urllib3/util/ssl_.py +++ b/venv/lib/python3.12/site-packages/urllib3/util/ssl_.py @@ -1,25 +1,28 @@ -from __future__ import annotations +from __future__ import absolute_import import hashlib import hmac import os -import socket import sys -import typing import warnings -from binascii import unhexlify +from binascii import hexlify, unhexlify -from ..exceptions import ProxySchemeUnsupported, SSLError -from .url import _BRACELESS_IPV6_ADDRZ_RE, _IPV4_RE +from ..exceptions import ( + InsecurePlatformWarning, + ProxySchemeUnsupported, + SNIMissingWarning, + SSLError, +) +from ..packages import six +from .url import BRACELESS_IPV6_ADDRZ_RE, IPV4_RE SSLContext = None SSLTransport = None -HAS_NEVER_CHECK_COMMON_NAME = False +HAS_SNI = False IS_PYOPENSSL = False +IS_SECURETRANSPORT = False ALPN_PROTOCOLS = ["http/1.1"] -_TYPE_VERSION_INFO = tuple[int, int, int, str, int] - # Maps the length of a digest to a possible hash function producing this digest HASHFUNC_MAP = { length: getattr(hashlib, algorithm, None) @@ -27,84 +30,159 @@ HASHFUNC_MAP = { } -def _is_has_never_check_common_name_reliable( - openssl_version: str, -) -> bool: - # As of May 2023, all released versions of LibreSSL fail to reject certificates with - # only common names, see https://github.com/urllib3/urllib3/pull/3024 - is_openssl = openssl_version.startswith("OpenSSL ") +def _const_compare_digest_backport(a, b): + """ + Compare two digests of equal length in constant time. - return is_openssl + The digests must be of type str/bytes. + Returns True if the digests match, and False otherwise. + """ + result = abs(len(a) - len(b)) + for left, right in zip(bytearray(a), bytearray(b)): + result |= left ^ right + return result == 0 -if typing.TYPE_CHECKING: - from ssl import VerifyMode - from typing import TypedDict +_const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport) - from .ssltransport import SSLTransport as SSLTransportType - - class _TYPE_PEER_CERT_RET_DICT(TypedDict, total=False): - subjectAltName: tuple[tuple[str, str], ...] - subject: tuple[tuple[tuple[str, str], ...], ...] - serialNumber: str - - -# Mapping from 'ssl.PROTOCOL_TLSX' to 'TLSVersion.X' -_SSL_VERSION_TO_TLS_VERSION: dict[int, int] = {} - -try: # Do we have ssl at all? +try: # Test for SSL features import ssl - from ssl import ( # type: ignore[assignment] - CERT_REQUIRED, - HAS_NEVER_CHECK_COMMON_NAME, - OP_NO_COMPRESSION, - OP_NO_TICKET, - OPENSSL_VERSION, - PROTOCOL_TLS, - PROTOCOL_TLS_CLIENT, - VERIFY_X509_PARTIAL_CHAIN, - VERIFY_X509_STRICT, - OP_NO_SSLv2, - OP_NO_SSLv3, - SSLContext, - TLSVersion, - ) + from ssl import CERT_REQUIRED, wrap_socket +except ImportError: + pass + +try: + from ssl import HAS_SNI # Has SNI? +except ImportError: + pass + +try: + from .ssltransport import SSLTransport +except ImportError: + pass + + +try: # Platform-specific: Python 3.6 + from ssl import PROTOCOL_TLS PROTOCOL_SSLv23 = PROTOCOL_TLS - - # Setting SSLContext.hostname_checks_common_name = False didn't work with - # LibreSSL, check details in the used function. - if HAS_NEVER_CHECK_COMMON_NAME and not _is_has_never_check_common_name_reliable( - OPENSSL_VERSION, - ): # Defensive: - HAS_NEVER_CHECK_COMMON_NAME = False - - # Need to be careful here in case old TLS versions get - # removed in future 'ssl' module implementations. - for attr in ("TLSv1", "TLSv1_1", "TLSv1_2"): - try: - _SSL_VERSION_TO_TLS_VERSION[getattr(ssl, f"PROTOCOL_{attr}")] = getattr( - TLSVersion, attr - ) - except AttributeError: # Defensive: - continue - - from .ssltransport import SSLTransport # type: ignore[assignment] except ImportError: - OP_NO_COMPRESSION = 0x20000 # type: ignore[assignment, misc] - OP_NO_TICKET = 0x4000 # type: ignore[assignment, misc] - OP_NO_SSLv2 = 0x1000000 # type: ignore[assignment, misc] - OP_NO_SSLv3 = 0x2000000 # type: ignore[assignment, misc] - PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 # type: ignore[assignment, misc] - PROTOCOL_TLS_CLIENT = 16 # type: ignore[assignment, misc] - VERIFY_X509_PARTIAL_CHAIN = 0x80000 # type: ignore[assignment,misc] - VERIFY_X509_STRICT = 0x20 # type: ignore[assignment, misc] + try: + from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS + + PROTOCOL_SSLv23 = PROTOCOL_TLS + except ImportError: + PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 + +try: + from ssl import PROTOCOL_TLS_CLIENT +except ImportError: + PROTOCOL_TLS_CLIENT = PROTOCOL_TLS -_TYPE_PEER_CERT_RET = typing.Union["_TYPE_PEER_CERT_RET_DICT", bytes, None] +try: + from ssl import OP_NO_COMPRESSION, OP_NO_SSLv2, OP_NO_SSLv3 +except ImportError: + OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 + OP_NO_COMPRESSION = 0x20000 -def assert_fingerprint(cert: bytes | None, fingerprint: str) -> None: +try: # OP_NO_TICKET was added in Python 3.6 + from ssl import OP_NO_TICKET +except ImportError: + OP_NO_TICKET = 0x4000 + + +# A secure default. +# Sources for more information on TLS ciphers: +# +# - https://wiki.mozilla.org/Security/Server_Side_TLS +# - https://www.ssllabs.com/projects/best-practices/index.html +# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ +# +# The general intent is: +# - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), +# - prefer ECDHE over DHE for better performance, +# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and +# security, +# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, +# - disable NULL authentication, MD5 MACs, DSS, and other +# insecure ciphers for security reasons. +# - NOTE: TLS 1.3 cipher suites are managed through a different interface +# not exposed by CPython (yet!) and are enabled by default if they're available. +DEFAULT_CIPHERS = ":".join( + [ + "ECDHE+AESGCM", + "ECDHE+CHACHA20", + "DHE+AESGCM", + "DHE+CHACHA20", + "ECDH+AESGCM", + "DH+AESGCM", + "ECDH+AES", + "DH+AES", + "RSA+AESGCM", + "RSA+AES", + "!aNULL", + "!eNULL", + "!MD5", + "!DSS", + ] +) + +try: + from ssl import SSLContext # Modern SSL? +except ImportError: + + class SSLContext(object): # Platform-specific: Python 2 + def __init__(self, protocol_version): + self.protocol = protocol_version + # Use default values from a real SSLContext + self.check_hostname = False + self.verify_mode = ssl.CERT_NONE + self.ca_certs = None + self.options = 0 + self.certfile = None + self.keyfile = None + self.ciphers = None + + def load_cert_chain(self, certfile, keyfile): + self.certfile = certfile + self.keyfile = keyfile + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + self.ca_certs = cafile + + if capath is not None: + raise SSLError("CA directories not supported in older Pythons") + + if cadata is not None: + raise SSLError("CA data not supported in older Pythons") + + def set_ciphers(self, cipher_suite): + self.ciphers = cipher_suite + + def wrap_socket(self, socket, server_hostname=None, server_side=False): + warnings.warn( + "A true SSLContext object is not available. This prevents " + "urllib3 from configuring SSL appropriately and may cause " + "certain SSL connections to fail. You can upgrade to a newer " + "version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + InsecurePlatformWarning, + ) + kwargs = { + "keyfile": self.keyfile, + "certfile": self.certfile, + "ca_certs": self.ca_certs, + "cert_reqs": self.verify_mode, + "ssl_version": self.protocol, + "server_side": server_side, + } + return wrap_socket(socket, ciphers=self.ciphers, **kwargs) + + +def assert_fingerprint(cert, fingerprint): """ Checks if given fingerprint matches the supplied certificate. @@ -114,17 +192,16 @@ def assert_fingerprint(cert: bytes | None, fingerprint: str) -> None: Fingerprint as string of hexdigits, can be interspersed by colons. """ - if cert is None: - raise SSLError("No certificate for the peer.") - fingerprint = fingerprint.replace(":", "").lower() digest_length = len(fingerprint) if digest_length not in HASHFUNC_MAP: - raise SSLError(f"Fingerprint of invalid length: {fingerprint}") + raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint)) hashfunc = HASHFUNC_MAP.get(digest_length) if hashfunc is None: raise SSLError( - f"Hash function implementation unavailable for fingerprint length: {digest_length}" + "Hash function implementation unavailable for fingerprint length: {0}".format( + digest_length + ) ) # We need encode() here for py32; works on py2 and p33. @@ -132,13 +209,15 @@ def assert_fingerprint(cert: bytes | None, fingerprint: str) -> None: cert_digest = hashfunc(cert).digest() - if not hmac.compare_digest(cert_digest, fingerprint_bytes): + if not _const_compare_digest(cert_digest, fingerprint_bytes): raise SSLError( - f'Fingerprints did not match. Expected "{fingerprint}", got "{cert_digest.hex()}"' + 'Fingerprints did not match. Expected "{0}", got "{1}".'.format( + fingerprint, hexlify(cert_digest) + ) ) -def resolve_cert_reqs(candidate: None | int | str) -> VerifyMode: +def resolve_cert_reqs(candidate): """ Resolves the argument to a numeric constant, which can be passed to the wrap_socket function/method from the ssl module. @@ -156,12 +235,12 @@ def resolve_cert_reqs(candidate: None | int | str) -> VerifyMode: res = getattr(ssl, candidate, None) if res is None: res = getattr(ssl, "CERT_" + candidate) - return res # type: ignore[no-any-return] + return res - return candidate # type: ignore[return-value] + return candidate -def resolve_ssl_version(candidate: None | int | str) -> int: +def resolve_ssl_version(candidate): """ like resolve_cert_reqs """ @@ -172,34 +251,35 @@ def resolve_ssl_version(candidate: None | int | str) -> int: res = getattr(ssl, candidate, None) if res is None: res = getattr(ssl, "PROTOCOL_" + candidate) - return typing.cast(int, res) + return res return candidate def create_urllib3_context( - ssl_version: int | None = None, - cert_reqs: int | None = None, - options: int | None = None, - ciphers: str | None = None, - ssl_minimum_version: int | None = None, - ssl_maximum_version: int | None = None, - verify_flags: int | None = None, -) -> ssl.SSLContext: - """Creates and configures an :class:`ssl.SSLContext` instance for use with urllib3. + ssl_version=None, cert_reqs=None, options=None, ciphers=None +): + """All arguments have the same meaning as ``ssl_wrap_socket``. + + By default, this function does a lot of the same work that + ``ssl.create_default_context`` does on Python 3.4+. It: + + - Disables SSLv2, SSLv3, and compression + - Sets a restricted set of server ciphers + + If you wish to enable SSLv3, you can do:: + + from urllib3.util import ssl_ + context = ssl_.create_urllib3_context() + context.options &= ~ssl_.OP_NO_SSLv3 + + You can do the same to enable compression (substituting ``COMPRESSION`` + for ``SSLv3`` in the last line above). :param ssl_version: The desired protocol version to use. This will default to PROTOCOL_SSLv23 which will negotiate the highest protocol that both the server and your installation of OpenSSL support. - - This parameter is deprecated instead use 'ssl_minimum_version'. - :param ssl_minimum_version: - The minimum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value. - :param ssl_maximum_version: - The maximum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value. - Not recommended to set to anything other than 'ssl.TLSVersion.MAXIMUM_SUPPORTED' which is the - default value. :param cert_reqs: Whether to require the certificate verification. This defaults to ``ssl.CERT_REQUIRED``. @@ -207,61 +287,18 @@ def create_urllib3_context( Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``. :param ciphers: - Which cipher suites to allow the server to select. Defaults to either system configured - ciphers if OpenSSL 1.1.1+, otherwise uses a secure default set of ciphers. - :param verify_flags: - The flags for certificate verification operations. These default to - ``ssl.VERIFY_X509_PARTIAL_CHAIN`` and ``ssl.VERIFY_X509_STRICT`` for Python 3.13+. + Which cipher suites to allow the server to select. :returns: Constructed SSLContext object with specified options :rtype: SSLContext """ - if SSLContext is None: - raise TypeError("Can't create an SSLContext object without an ssl module") + # PROTOCOL_TLS is deprecated in Python 3.10 + if not ssl_version or ssl_version == PROTOCOL_TLS: + ssl_version = PROTOCOL_TLS_CLIENT - # This means 'ssl_version' was specified as an exact value. - if ssl_version not in (None, PROTOCOL_TLS, PROTOCOL_TLS_CLIENT): - # Disallow setting 'ssl_version' and 'ssl_minimum|maximum_version' - # to avoid conflicts. - if ssl_minimum_version is not None or ssl_maximum_version is not None: - raise ValueError( - "Can't specify both 'ssl_version' and either " - "'ssl_minimum_version' or 'ssl_maximum_version'" - ) + context = SSLContext(ssl_version) - # 'ssl_version' is deprecated and will be removed in the future. - else: - # Use 'ssl_minimum_version' and 'ssl_maximum_version' instead. - ssl_minimum_version = _SSL_VERSION_TO_TLS_VERSION.get( - ssl_version, TLSVersion.MINIMUM_SUPPORTED - ) - ssl_maximum_version = _SSL_VERSION_TO_TLS_VERSION.get( - ssl_version, TLSVersion.MAXIMUM_SUPPORTED - ) - - # This warning message is pushing users to use 'ssl_minimum_version' - # instead of both min/max. Best practice is to only set the minimum version and - # keep the maximum version to be it's default value: 'TLSVersion.MAXIMUM_SUPPORTED' - warnings.warn( - "'ssl_version' option is deprecated and will be " - "removed in urllib3 v3.0. Instead use 'ssl_minimum_version'", - category=FutureWarning, - stacklevel=2, - ) - - context = SSLContext(PROTOCOL_TLS_CLIENT) - if ssl_minimum_version is not None: - context.minimum_version = ssl_minimum_version - else: # pyOpenSSL defaults to 'MINIMUM_SUPPORTED' so explicitly set TLSv1.2 here - context.minimum_version = TLSVersion.TLSv1_2 - - if ssl_maximum_version is not None: - context.maximum_version = ssl_maximum_version - - # Unless we're given ciphers defer to either system ciphers in - # the case of OpenSSL 1.1.1+ or use our own secure default ciphers. - if ciphers: - context.set_ciphers(ciphers) + context.set_ciphers(ciphers or DEFAULT_CIPHERS) # Setting the default here, as we may have no ssl module on import cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs @@ -283,103 +320,65 @@ def create_urllib3_context( context.options |= options - if verify_flags is None: - verify_flags = 0 - # In Python 3.13+ ssl.create_default_context() sets VERIFY_X509_PARTIAL_CHAIN - # and VERIFY_X509_STRICT so we do the same - if sys.version_info >= (3, 13): - verify_flags |= VERIFY_X509_PARTIAL_CHAIN - verify_flags |= VERIFY_X509_STRICT - - context.verify_flags |= verify_flags - # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is # necessary for conditional client cert authentication with TLS 1.3. - # The attribute is None for OpenSSL <= 1.1.0 or does not exist when using - # an SSLContext created by pyOpenSSL. - if getattr(context, "post_handshake_auth", None) is not None: + # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older + # versions of Python. We only enable on Python 3.7.4+ or if certificate + # verification is enabled to work around Python issue #37428 + # See: https://bugs.python.org/issue37428 + if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr( + context, "post_handshake_auth", None + ) is not None: context.post_handshake_auth = True + def disable_check_hostname(): + if ( + getattr(context, "check_hostname", None) is not None + ): # Platform-specific: Python 3.2 + # We do our own verification, including fingerprints and alternative + # hostnames. So disable it here + context.check_hostname = False + # The order of the below lines setting verify_mode and check_hostname # matter due to safe-guards SSLContext has to prevent an SSLContext with - # check_hostname=True, verify_mode=NONE/OPTIONAL. - # We always set 'check_hostname=False' for pyOpenSSL so we rely on our own - # 'ssl.match_hostname()' implementation. - if cert_reqs == ssl.CERT_REQUIRED and not IS_PYOPENSSL: + # check_hostname=True, verify_mode=NONE/OPTIONAL. This is made even more + # complex because we don't know whether PROTOCOL_TLS_CLIENT will be used + # or not so we don't know the initial state of the freshly created SSLContext. + if cert_reqs == ssl.CERT_REQUIRED: context.verify_mode = cert_reqs - context.check_hostname = True + disable_check_hostname() else: - context.check_hostname = False + disable_check_hostname() context.verify_mode = cert_reqs - context.hostname_checks_common_name = False - - if "SSLKEYLOGFILE" in os.environ: - sslkeylogfile = os.path.expandvars(os.environ.get("SSLKEYLOGFILE")) - else: - sslkeylogfile = None - if sslkeylogfile: - context.keylog_filename = sslkeylogfile + # Enable logging of TLS session keys via defacto standard environment variable + # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values. + if hasattr(context, "keylog_filename"): + sslkeylogfile = os.environ.get("SSLKEYLOGFILE") + if sslkeylogfile: + context.keylog_filename = sslkeylogfile return context -@typing.overload def ssl_wrap_socket( - sock: socket.socket, - keyfile: str | None = ..., - certfile: str | None = ..., - cert_reqs: int | None = ..., - ca_certs: str | None = ..., - server_hostname: str | None = ..., - ssl_version: int | None = ..., - ciphers: str | None = ..., - ssl_context: ssl.SSLContext | None = ..., - ca_cert_dir: str | None = ..., - key_password: str | None = ..., - ca_cert_data: None | str | bytes = ..., - tls_in_tls: typing.Literal[False] = ..., -) -> ssl.SSLSocket: ... - - -@typing.overload -def ssl_wrap_socket( - sock: socket.socket, - keyfile: str | None = ..., - certfile: str | None = ..., - cert_reqs: int | None = ..., - ca_certs: str | None = ..., - server_hostname: str | None = ..., - ssl_version: int | None = ..., - ciphers: str | None = ..., - ssl_context: ssl.SSLContext | None = ..., - ca_cert_dir: str | None = ..., - key_password: str | None = ..., - ca_cert_data: None | str | bytes = ..., - tls_in_tls: bool = ..., -) -> ssl.SSLSocket | SSLTransportType: ... - - -def ssl_wrap_socket( - sock: socket.socket, - keyfile: str | None = None, - certfile: str | None = None, - cert_reqs: int | None = None, - ca_certs: str | None = None, - server_hostname: str | None = None, - ssl_version: int | None = None, - ciphers: str | None = None, - ssl_context: ssl.SSLContext | None = None, - ca_cert_dir: str | None = None, - key_password: str | None = None, - ca_cert_data: None | str | bytes = None, - tls_in_tls: bool = False, -) -> ssl.SSLSocket | SSLTransportType: + sock, + keyfile=None, + certfile=None, + cert_reqs=None, + ca_certs=None, + server_hostname=None, + ssl_version=None, + ciphers=None, + ssl_context=None, + ca_cert_dir=None, + key_password=None, + ca_cert_data=None, + tls_in_tls=False, +): """ - All arguments except for server_hostname, ssl_context, tls_in_tls, ca_cert_data and - ca_cert_dir have the same meaning as they do when using - :func:`ssl.create_default_context`, :meth:`ssl.SSLContext.load_cert_chain`, - :meth:`ssl.SSLContext.set_ciphers` and :meth:`ssl.SSLContext.wrap_socket`. + All arguments except for server_hostname, ssl_context, and ca_cert_dir have + the same meaning as they do when using :func:`ssl.wrap_socket`. :param server_hostname: When SNI is supported, the expected hostname of the certificate @@ -402,18 +401,19 @@ def ssl_wrap_socket( """ context = ssl_context if context is None: - # Note: This branch of code and all the variables in it are only used in tests. - # We should consider deprecating and removing this code. + # Note: This branch of code and all the variables in it are no longer + # used by urllib3 itself. We should consider deprecating and removing + # this code. context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) if ca_certs or ca_cert_dir or ca_cert_data: try: context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data) - except OSError as e: - raise SSLError(e) from e + except (IOError, OSError) as e: + raise SSLError(e) elif ssl_context is None and hasattr(context, "load_default_certs"): - # try to load OS default certs; works well on Windows. + # try to load OS default certs; works well on Windows (require Python3.4+) context.load_default_certs() # Attempt to detect if we get the goofy behavior of the @@ -428,28 +428,57 @@ def ssl_wrap_socket( else: context.load_cert_chain(certfile, keyfile, key_password) - context.set_alpn_protocols(ALPN_PROTOCOLS) + try: + if hasattr(context, "set_alpn_protocols"): + context.set_alpn_protocols(ALPN_PROTOCOLS) + except NotImplementedError: # Defensive: in CI, we always have set_alpn_protocols + pass - ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname) + # If we detect server_hostname is an IP address then the SNI + # extension should not be used according to RFC3546 Section 3.1 + use_sni_hostname = server_hostname and not is_ipaddress(server_hostname) + # SecureTransport uses server_hostname in certificate verification. + send_sni = (use_sni_hostname and HAS_SNI) or ( + IS_SECURETRANSPORT and server_hostname + ) + # Do not warn the user if server_hostname is an invalid SNI hostname. + if not HAS_SNI and use_sni_hostname: + warnings.warn( + "An HTTPS request has been made, but the SNI (Server Name " + "Indication) extension to TLS is not available on this platform. " + "This may cause the server to present an incorrect TLS " + "certificate, which can cause validation failures. You can upgrade to " + "a newer version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + SNIMissingWarning, + ) + + if send_sni: + ssl_sock = _ssl_wrap_socket_impl( + sock, context, tls_in_tls, server_hostname=server_hostname + ) + else: + ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls) return ssl_sock -def is_ipaddress(hostname: str | bytes) -> bool: +def is_ipaddress(hostname): """Detects whether the hostname given is an IPv4 or IPv6 address. Also detects IPv6 addresses with Zone IDs. :param str hostname: Hostname to examine. :return: True if the hostname is an IP address, False otherwise. """ - if isinstance(hostname, bytes): + if not six.PY2 and isinstance(hostname, bytes): # IDN A-label bytes are ASCII compatible. hostname = hostname.decode("ascii") - return bool(_IPV4_RE.match(hostname) or _BRACELESS_IPV6_ADDRZ_RE.match(hostname)) + return bool(IPV4_RE.match(hostname) or BRACELESS_IPV6_ADDRZ_RE.match(hostname)) -def _is_key_file_encrypted(key_file: str) -> bool: +def _is_key_file_encrypted(key_file): """Detects if a key file is encrypted or not.""" - with open(key_file) as f: + with open(key_file, "r") as f: for line in f: # Look for Proc-Type: 4,ENCRYPTED if "ENCRYPTED" in line: @@ -458,12 +487,7 @@ def _is_key_file_encrypted(key_file: str) -> bool: return False -def _ssl_wrap_socket_impl( - sock: socket.socket, - ssl_context: ssl.SSLContext, - tls_in_tls: bool, - server_hostname: str | None = None, -) -> ssl.SSLSocket | SSLTransportType: +def _ssl_wrap_socket_impl(sock, ssl_context, tls_in_tls, server_hostname=None): if tls_in_tls: if not SSLTransport: # Import error, ssl is not available. @@ -474,4 +498,7 @@ def _ssl_wrap_socket_impl( SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context) return SSLTransport(sock, ssl_context, server_hostname) - return ssl_context.wrap_socket(sock, server_hostname=server_hostname) + if server_hostname: + return ssl_context.wrap_socket(sock, server_hostname=server_hostname) + else: + return ssl_context.wrap_socket(sock) diff --git a/venv/lib/python3.12/site-packages/urllib3/util/ssl_match_hostname.py b/venv/lib/python3.12/site-packages/urllib3/util/ssl_match_hostname.py index 94994f2..1dd950c 100644 --- a/venv/lib/python3.12/site-packages/urllib3/util/ssl_match_hostname.py +++ b/venv/lib/python3.12/site-packages/urllib3/util/ssl_match_hostname.py @@ -1,18 +1,19 @@ -"""The match_hostname() function from Python 3.5, essential when using SSL.""" +"""The match_hostname() function from Python 3.3.3, essential when using SSL.""" # Note: This file is under the PSF license as the code comes from the python # stdlib. http://docs.python.org/3/license.html -# It is modified to remove commonName support. -from __future__ import annotations - -import ipaddress import re -import typing -from ipaddress import IPv4Address, IPv6Address +import sys -if typing.TYPE_CHECKING: - from .ssl_ import _TYPE_PEER_CERT_RET_DICT +# ipaddress has been backported to 2.6+ in pypi. If it is installed on the +# system, use it to handle IPAddress ServerAltnames (this was added in +# python-3.5) otherwise only do DNS matching. This allows +# util.ssl_match_hostname to continue to be used in Python 2.7. +try: + import ipaddress +except ImportError: + ipaddress = None __version__ = "3.5.0.1" @@ -21,9 +22,7 @@ class CertificateError(ValueError): pass -def _dnsname_match( - dn: typing.Any, hostname: str, max_wildcards: int = 1 -) -> typing.Match[str] | None | bool: +def _dnsname_match(dn, hostname, max_wildcards=1): """Matching according to RFC 6125, section 6.4.3 http://tools.ietf.org/html/rfc6125#section-6.4.3 @@ -50,7 +49,7 @@ def _dnsname_match( # speed up common case w/o wildcards if not wildcards: - return bool(dn.lower() == hostname.lower()) + return dn.lower() == hostname.lower() # RFC 6125, section 6.4.3, subitem 1. # The client SHOULD NOT attempt to match a presented identifier in which @@ -77,26 +76,26 @@ def _dnsname_match( return pat.match(hostname) -def _ipaddress_match(ipname: str, host_ip: IPv4Address | IPv6Address) -> bool: +def _to_unicode(obj): + if isinstance(obj, str) and sys.version_info < (3,): + # ignored flake8 # F821 to support python 2.7 function + obj = unicode(obj, encoding="ascii", errors="strict") # noqa: F821 + return obj + + +def _ipaddress_match(ipname, host_ip): """Exact matching of IP addresses. - RFC 9110 section 4.3.5: "A reference identity of IP-ID contains the decoded - bytes of the IP address. An IP version 4 address is 4 octets, and an IP - version 6 address is 16 octets. [...] A reference identity of type IP-ID - matches if the address is identical to an iPAddress value of the - subjectAltName extension of the certificate." + RFC 6125 explicitly doesn't define an algorithm for this + (section 1.7.2 - "Out of Scope"). """ # OpenSSL may add a trailing newline to a subjectAltName's IP address # Divergence from upstream: ipaddress can't handle byte str - ip = ipaddress.ip_address(ipname.rstrip()) - return bool(ip.packed == host_ip.packed) + ip = ipaddress.ip_address(_to_unicode(ipname).rstrip()) + return ip == host_ip -def match_hostname( - cert: _TYPE_PEER_CERT_RET_DICT | None, - hostname: str, - hostname_checks_common_name: bool = False, -) -> None: +def match_hostname(cert, hostname): """Verify that *cert* (in decoded format as returned by SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 rules are followed, but IP addresses are not accepted for *hostname*. @@ -110,16 +109,23 @@ def match_hostname( "SSL socket or SSL context with either " "CERT_OPTIONAL or CERT_REQUIRED" ) - try: - host_ip = ipaddress.ip_address(hostname) - except ValueError: - # Not an IP address (common case) + # Divergence from upstream: ipaddress can't handle byte str + host_ip = ipaddress.ip_address(_to_unicode(hostname)) + except (UnicodeError, ValueError): + # ValueError: Not an IP address (common case) + # UnicodeError: Divergence from upstream: Have to deal with ipaddress not taking + # byte strings. addresses should be all ascii, so we consider it not + # an ipaddress in this case host_ip = None + except AttributeError: + # Divergence from upstream: Make ipaddress library optional + if ipaddress is None: + host_ip = None + else: # Defensive + raise dnsnames = [] - san: tuple[tuple[str, str], ...] = cert.get("subjectAltName", ()) - key: str - value: str + san = cert.get("subjectAltName", ()) for key, value in san: if key == "DNS": if host_ip is None and _dnsname_match(value, hostname): @@ -129,25 +135,25 @@ def match_hostname( if host_ip is not None and _ipaddress_match(value, host_ip): return dnsnames.append(value) - - # We only check 'commonName' if it's enabled and we're not verifying - # an IP address. IP addresses aren't valid within 'commonName'. - if hostname_checks_common_name and host_ip is None and not dnsnames: + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName for sub in cert.get("subject", ()): for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. if key == "commonName": if _dnsname_match(value, hostname): return - dnsnames.append( - value - ) # Defensive: for older PyPy and OpenSSL versions - + dnsnames.append(value) if len(dnsnames) > 1: raise CertificateError( "hostname %r " "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames))) ) elif len(dnsnames) == 1: - raise CertificateError(f"hostname {hostname!r} doesn't match {dnsnames[0]!r}") + raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0])) else: - raise CertificateError("no appropriate subjectAltName fields were found") + raise CertificateError( + "no appropriate commonName or subjectAltName fields were found" + ) diff --git a/venv/lib/python3.12/site-packages/urllib3/util/ssltransport.py b/venv/lib/python3.12/site-packages/urllib3/util/ssltransport.py index 6d59bc3..4a7105d 100644 --- a/venv/lib/python3.12/site-packages/urllib3/util/ssltransport.py +++ b/venv/lib/python3.12/site-packages/urllib3/util/ssltransport.py @@ -1,20 +1,9 @@ -from __future__ import annotations - import io import socket import ssl -import typing from ..exceptions import ProxySchemeUnsupported - -if typing.TYPE_CHECKING: - from typing_extensions import Self - - from .ssl_ import _TYPE_PEER_CERT_RET, _TYPE_PEER_CERT_RET_DICT - - -_WriteBuffer = typing.Union[bytearray, memoryview] -_ReturnValue = typing.TypeVar("_ReturnValue") +from ..packages import six SSL_BLOCKSIZE = 16384 @@ -31,7 +20,7 @@ class SSLTransport: """ @staticmethod - def _validate_ssl_context_for_tls_in_tls(ssl_context: ssl.SSLContext) -> None: + def _validate_ssl_context_for_tls_in_tls(ssl_context): """ Raises a ProxySchemeUnsupported if the provided ssl_context can't be used for TLS in TLS. @@ -41,18 +30,20 @@ class SSLTransport: """ if not hasattr(ssl_context, "wrap_bio"): - raise ProxySchemeUnsupported( - "TLS in TLS requires SSLContext.wrap_bio() which isn't " - "available on non-native SSLContext" - ) + if six.PY2: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "supported on Python 2" + ) + else: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "available on non-native SSLContext" + ) def __init__( - self, - socket: socket.socket, - ssl_context: ssl.SSLContext, - server_hostname: str | None = None, - suppress_ragged_eofs: bool = True, - ) -> None: + self, socket, ssl_context, server_hostname=None, suppress_ragged_eofs=True + ): """ Create an SSLTransport around socket using the provided ssl_context. """ @@ -69,36 +60,33 @@ class SSLTransport: # Perform initial handshake. self._ssl_io_loop(self.sslobj.do_handshake) - def __enter__(self) -> Self: + def __enter__(self): return self - def __exit__(self, *_: typing.Any) -> None: + def __exit__(self, *_): self.close() - def fileno(self) -> int: + def fileno(self): return self.socket.fileno() - def read(self, len: int = 1024, buffer: typing.Any | None = None) -> int | bytes: + def read(self, len=1024, buffer=None): return self._wrap_ssl_read(len, buffer) - def recv(self, buflen: int = 1024, flags: int = 0) -> int | bytes: + 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(buflen) + return self._wrap_ssl_read(len) - def recv_into( - self, - buffer: _WriteBuffer, - nbytes: int | None = None, - flags: int = 0, - ) -> None | int | bytes: + 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 nbytes is None: + if buffer and (nbytes is None): nbytes = len(buffer) + elif nbytes is None: + nbytes = 1024 return self.read(nbytes, buffer) - def sendall(self, data: bytes, flags: int = 0) -> None: + def sendall(self, data, flags=0): if flags != 0: raise ValueError("non-zero flags not allowed in calls to sendall") count = 0 @@ -108,20 +96,15 @@ class SSLTransport: v = self.send(byte_view[count:]) count += v - def send(self, data: bytes, flags: int = 0) -> int: + def send(self, data, flags=0): if flags != 0: raise ValueError("non-zero flags not allowed in calls to send") - return self._ssl_io_loop(self.sslobj.write, data) + response = self._ssl_io_loop(self.sslobj.write, data) + return response def makefile( - self, - mode: str, - buffering: int | None = None, - *, - encoding: str | None = None, - errors: str | None = None, - newline: str | None = None, - ) -> typing.BinaryIO | typing.TextIO | socket.SocketIO: + 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. @@ -130,7 +113,7 @@ class SSLTransport: changes to point to the socket directly. """ if not set(mode) <= {"r", "w", "b"}: - raise ValueError(f"invalid mode {mode!r} (only r, w, b allowed)") + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) writing = "w" in mode reading = "r" in mode or not writing @@ -141,8 +124,8 @@ class SSLTransport: rawmode += "r" if writing: rawmode += "w" - raw = socket.SocketIO(self, rawmode) # type: ignore[arg-type] - self.socket._io_refs += 1 # type: ignore[attr-defined] + raw = socket.SocketIO(self, rawmode) + self.socket._io_refs += 1 if buffering is None: buffering = -1 if buffering < 0: @@ -151,9 +134,8 @@ class SSLTransport: if not binary: raise ValueError("unbuffered streams must be binary") return raw - buffer: typing.BinaryIO if reading and writing: - buffer = io.BufferedRWPair(raw, raw, buffering) # type: ignore[assignment] + buffer = io.BufferedRWPair(raw, raw, buffering) elif reading: buffer = io.BufferedReader(raw, buffering) else: @@ -162,51 +144,46 @@ class SSLTransport: if binary: return buffer text = io.TextIOWrapper(buffer, encoding, errors, newline) - text.mode = mode # type: ignore[misc] + text.mode = mode return text - def unwrap(self) -> None: + def unwrap(self): self._ssl_io_loop(self.sslobj.unwrap) - def close(self) -> None: + def close(self): self.socket.close() - @typing.overload - def getpeercert( - self, binary_form: typing.Literal[False] = ... - ) -> _TYPE_PEER_CERT_RET_DICT | None: ... + def getpeercert(self, binary_form=False): + return self.sslobj.getpeercert(binary_form) - @typing.overload - def getpeercert(self, binary_form: typing.Literal[True]) -> bytes | None: ... - - def getpeercert(self, binary_form: bool = False) -> _TYPE_PEER_CERT_RET: - return self.sslobj.getpeercert(binary_form) # type: ignore[return-value] - - def version(self) -> str | None: + def version(self): return self.sslobj.version() - def cipher(self) -> tuple[str, str, int] | None: + def cipher(self): return self.sslobj.cipher() - def selected_alpn_protocol(self) -> str | None: + def selected_alpn_protocol(self): return self.sslobj.selected_alpn_protocol() - def shared_ciphers(self) -> list[tuple[str, str, int]] | None: + def selected_npn_protocol(self): + return self.sslobj.selected_npn_protocol() + + def shared_ciphers(self): return self.sslobj.shared_ciphers() - def compression(self) -> str | None: + def compression(self): return self.sslobj.compression() - def settimeout(self, value: float | None) -> None: + def settimeout(self, value): self.socket.settimeout(value) - def gettimeout(self) -> float | None: + def gettimeout(self): return self.socket.gettimeout() - def _decref_socketios(self) -> None: - self.socket._decref_socketios() # type: ignore[attr-defined] + def _decref_socketios(self): + self.socket._decref_socketios() - def _wrap_ssl_read(self, len: int, buffer: bytearray | None = None) -> int | bytes: + def _wrap_ssl_read(self, len, buffer=None): try: return self._ssl_io_loop(self.sslobj.read, len, buffer) except ssl.SSLError as e: @@ -215,29 +192,7 @@ class SSLTransport: else: raise - # func is sslobj.do_handshake or sslobj.unwrap - @typing.overload - def _ssl_io_loop(self, func: typing.Callable[[], None]) -> None: ... - - # func is sslobj.write, arg1 is data - @typing.overload - def _ssl_io_loop(self, func: typing.Callable[[bytes], int], arg1: bytes) -> int: ... - - # func is sslobj.read, arg1 is len, arg2 is buffer - @typing.overload - def _ssl_io_loop( - self, - func: typing.Callable[[int, bytearray | None], bytes], - arg1: int, - arg2: bytearray | None, - ) -> bytes: ... - - def _ssl_io_loop( - self, - func: typing.Callable[..., _ReturnValue], - arg1: None | bytes | int = None, - arg2: bytearray | None = None, - ) -> _ReturnValue: + def _ssl_io_loop(self, func, *args): """Performs an I/O loop between incoming/outgoing and the socket.""" should_loop = True ret = None @@ -245,12 +200,7 @@ class SSLTransport: while should_loop: errno = None try: - if arg1 is None and arg2 is None: - ret = func() - elif arg2 is None: - ret = func(arg1) - else: - ret = func(arg1, arg2) + 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. @@ -268,4 +218,4 @@ class SSLTransport: self.incoming.write(buf) else: self.incoming.write_eof() - return typing.cast(_ReturnValue, ret) + return ret diff --git a/venv/lib/python3.12/site-packages/urllib3/util/timeout.py b/venv/lib/python3.12/site-packages/urllib3/util/timeout.py index 4bb1be1..78e18a6 100644 --- a/venv/lib/python3.12/site-packages/urllib3/util/timeout.py +++ b/venv/lib/python3.12/site-packages/urllib3/util/timeout.py @@ -1,56 +1,44 @@ -from __future__ import annotations +from __future__ import absolute_import import time -import typing -from enum import Enum -from socket import getdefaulttimeout + +# The default socket timeout, used by httplib to indicate that no timeout was; specified by the user +from socket import _GLOBAL_DEFAULT_TIMEOUT, getdefaulttimeout from ..exceptions import TimeoutStateError -if typing.TYPE_CHECKING: - from typing import Final +# A sentinel value to indicate that no timeout was specified by the user in +# urllib3 +_Default = object() -class _TYPE_DEFAULT(Enum): - # This value should never be passed to socket.settimeout() so for safety we use a -1. - # socket.settimout() raises a ValueError for negative values. - token = -1 +# Use time.monotonic if available. +current_time = getattr(time, "monotonic", time.time) -_DEFAULT_TIMEOUT: Final[_TYPE_DEFAULT] = _TYPE_DEFAULT.token - -_TYPE_TIMEOUT = typing.Optional[typing.Union[float, _TYPE_DEFAULT]] - - -class Timeout: +class Timeout(object): """Timeout configuration. Timeouts can be defined as a default for a pool: .. code-block:: python - import urllib3 - - timeout = urllib3.util.Timeout(connect=2.0, read=7.0) - - http = urllib3.PoolManager(timeout=timeout) - - resp = http.request("GET", "https://example.com/") - - print(resp.status) + timeout = Timeout(connect=2.0, read=7.0) + http = PoolManager(timeout=timeout) + response = http.request('GET', 'http://example.com/') Or per-request (which overrides the default for the pool): .. code-block:: python - response = http.request("GET", "https://example.com/", timeout=Timeout(10)) + response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) Timeouts can be disabled by setting all the parameters to ``None``: .. code-block:: python no_timeout = Timeout(connect=None, read=None) - response = http.request("GET", "https://example.com/", timeout=no_timeout) + response = http.request('GET', 'http://example.com/, timeout=no_timeout) :param total: @@ -101,34 +89,38 @@ class Timeout: the case; if a server streams one byte every fifteen seconds, a timeout of 20 seconds will not trigger, even though the request will take several minutes to complete. + + If your goal is to cut off any request after a set amount of wall clock + time, consider having a second "watcher" thread to cut off a slow + request. """ #: A sentinel object representing the default timeout value - DEFAULT_TIMEOUT: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT + DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT - def __init__( - self, - total: _TYPE_TIMEOUT = None, - connect: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - read: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - ) -> None: + def __init__(self, total=None, connect=_Default, read=_Default): self._connect = self._validate_timeout(connect, "connect") self._read = self._validate_timeout(read, "read") self.total = self._validate_timeout(total, "total") - self._start_connect: float | None = None + self._start_connect = None - def __repr__(self) -> str: - return f"{type(self).__name__}(connect={self._connect!r}, read={self._read!r}, total={self.total!r})" + def __repr__(self): + return "%s(connect=%r, read=%r, total=%r)" % ( + type(self).__name__, + self._connect, + self._read, + self.total, + ) # __str__ provided for backwards compatibility __str__ = __repr__ - @staticmethod - def resolve_default_timeout(timeout: _TYPE_TIMEOUT) -> float | None: - return getdefaulttimeout() if timeout is _DEFAULT_TIMEOUT else timeout + @classmethod + def resolve_default_timeout(cls, timeout): + return getdefaulttimeout() if timeout is cls.DEFAULT_TIMEOUT else timeout @classmethod - def _validate_timeout(cls, value: _TYPE_TIMEOUT, name: str) -> _TYPE_TIMEOUT: + def _validate_timeout(cls, value, name): """Check that a timeout attribute is valid. :param value: The timeout value to validate @@ -138,7 +130,10 @@ class Timeout: :raises ValueError: If it is a numeric value less than or equal to zero, or the type is not an integer, float, or None. """ - if value is None or value is _DEFAULT_TIMEOUT: + if value is _Default: + return cls.DEFAULT_TIMEOUT + + if value is None or value is cls.DEFAULT_TIMEOUT: return value if isinstance(value, bool): @@ -152,7 +147,7 @@ class Timeout: raise ValueError( "Timeout value %s was %s, but it must be an " "int, float or None." % (name, value) - ) from None + ) try: if value <= 0: @@ -162,15 +157,16 @@ class Timeout: "than or equal to 0." % (name, value) ) except TypeError: + # Python 3 raise ValueError( "Timeout value %s was %s, but it must be an " "int, float or None." % (name, value) - ) from None + ) return value @classmethod - def from_float(cls, timeout: _TYPE_TIMEOUT) -> Timeout: + def from_float(cls, timeout): """Create a new Timeout from a legacy timeout value. The timeout value used by httplib.py sets the same timeout on the @@ -179,13 +175,13 @@ class Timeout: passed to this function. :param timeout: The legacy timeout value. - :type timeout: integer, float, :attr:`urllib3.util.Timeout.DEFAULT_TIMEOUT`, or None + :type timeout: integer, float, sentinel default object, or None :return: Timeout object :rtype: :class:`Timeout` """ return Timeout(read=timeout, connect=timeout) - def clone(self) -> Timeout: + def clone(self): """Create a copy of the timeout object Timeout properties are stored per-pool but each request needs a fresh @@ -199,7 +195,7 @@ class Timeout: # detect the user default. return Timeout(connect=self._connect, read=self._read, total=self.total) - def start_connect(self) -> float: + def start_connect(self): """Start the timeout clock, used during a connect() attempt :raises urllib3.exceptions.TimeoutStateError: if you attempt @@ -207,10 +203,10 @@ class Timeout: """ if self._start_connect is not None: raise TimeoutStateError("Timeout timer has already been started.") - self._start_connect = time.monotonic() + self._start_connect = current_time() return self._start_connect - def get_connect_duration(self) -> float: + def get_connect_duration(self): """Gets the time elapsed since the call to :meth:`start_connect`. :return: Elapsed time in seconds. @@ -222,10 +218,10 @@ class Timeout: raise TimeoutStateError( "Can't get connect duration for timer that has not started." ) - return time.monotonic() - self._start_connect + return current_time() - self._start_connect @property - def connect_timeout(self) -> _TYPE_TIMEOUT: + def connect_timeout(self): """Get the value to use when setting a connection timeout. This will be a positive float or integer, the value None @@ -237,13 +233,13 @@ class Timeout: if self.total is None: return self._connect - if self._connect is None or self._connect is _DEFAULT_TIMEOUT: + if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: return self.total - return min(self._connect, self.total) # type: ignore[type-var] + return min(self._connect, self.total) @property - def read_timeout(self) -> float | None: + def read_timeout(self): """Get the value for the read timeout. This assumes some time has elapsed in the connection timeout and @@ -255,21 +251,21 @@ class Timeout: raised. :return: Value to use for the read timeout. - :rtype: int, float or None + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` has not yet been called on this object. """ if ( self.total is not None - and self.total is not _DEFAULT_TIMEOUT + and self.total is not self.DEFAULT_TIMEOUT and self._read is not None - and self._read is not _DEFAULT_TIMEOUT + and self._read is not self.DEFAULT_TIMEOUT ): # In case the connect timeout has not yet been established. if self._start_connect is None: return self._read return max(0, min(self.total - self.get_connect_duration(), self._read)) - elif self.total is not None and self.total is not _DEFAULT_TIMEOUT: + elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: return max(0, self.total - self.get_connect_duration()) else: - return self.resolve_default_timeout(self._read) + return self._read diff --git a/venv/lib/python3.12/site-packages/urllib3/util/url.py b/venv/lib/python3.12/site-packages/urllib3/util/url.py index db057f1..e5682d3 100644 --- a/venv/lib/python3.12/site-packages/urllib3/util/url.py +++ b/venv/lib/python3.12/site-packages/urllib3/util/url.py @@ -1,20 +1,22 @@ -from __future__ import annotations +from __future__ import absolute_import import re -import typing +from collections import namedtuple from ..exceptions import LocationParseError -from .util import to_str +from ..packages import six + +url_attrs = ["scheme", "auth", "host", "port", "path", "query", "fragment"] # We only want to normalize urls with an HTTP(S) scheme. # urllib3 infers URLs without a scheme (None) to be http. -_NORMALIZABLE_SCHEMES = ("http", "https", None) +NORMALIZABLE_SCHEMES = ("http", "https", None) # Almost all of these patterns were derived from the # 'rfc3986' module: https://github.com/python-hyper/rfc3986 -_PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") -_SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") -_URI_RE = re.compile( +PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") +SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") +URI_RE = re.compile( r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?" r"(?://([^\\/?#]*))?" r"([^?#]*)" @@ -23,10 +25,10 @@ _URI_RE = re.compile( re.UNICODE | re.DOTALL, ) -_IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" -_HEX_PAT = "[0-9A-Fa-f]{1,4}" -_LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=_HEX_PAT, ipv4=_IPV4_PAT) -_subs = {"hex": _HEX_PAT, "ls32": _LS32_PAT} +IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" +HEX_PAT = "[0-9A-Fa-f]{1,4}" +LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT) +_subs = {"hex": HEX_PAT, "ls32": LS32_PAT} _variations = [ # 6( h16 ":" ) ls32 "(?:%(hex)s:){6}%(ls32)s", @@ -48,78 +50,69 @@ _variations = [ "(?:(?:%(hex)s:){0,6}%(hex)s)?::", ] -_UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._\-~" -_IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" -_ZONE_ID_PAT = "(?:%25|%)(?:[" + _UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" -_IPV6_ADDRZ_PAT = r"\[" + _IPV6_PAT + r"(?:" + _ZONE_ID_PAT + r")?\]" -_REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" -_TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") +UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._\-~" +IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" +ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" +IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]" +REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" +TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") -_IPV4_RE = re.compile("^" + _IPV4_PAT + "$") -_IPV6_RE = re.compile("^" + _IPV6_PAT + "$") -_IPV6_ADDRZ_RE = re.compile("^" + _IPV6_ADDRZ_PAT + "$") -_BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + _IPV6_ADDRZ_PAT[2:-2] + "$") -_ZONE_ID_RE = re.compile("(" + _ZONE_ID_PAT + r")\]$") +IPV4_RE = re.compile("^" + IPV4_PAT + "$") +IPV6_RE = re.compile("^" + IPV6_PAT + "$") +IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$") +BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT[2:-2] + "$") +ZONE_ID_RE = re.compile("(" + ZONE_ID_PAT + r")\]$") _HOST_PORT_PAT = ("^(%s|%s|%s)(?::0*?(|0|[1-9][0-9]{0,4}))?$") % ( - _REG_NAME_PAT, - _IPV4_PAT, - _IPV6_ADDRZ_PAT, + REG_NAME_PAT, + IPV4_PAT, + IPV6_ADDRZ_PAT, ) _HOST_PORT_RE = re.compile(_HOST_PORT_PAT, re.UNICODE | re.DOTALL) -_UNRESERVED_CHARS = set( +UNRESERVED_CHARS = set( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~" ) -_SUB_DELIM_CHARS = set("!$&'()*+,;=") -_USERINFO_CHARS = _UNRESERVED_CHARS | _SUB_DELIM_CHARS | {":"} -_PATH_CHARS = _USERINFO_CHARS | {"@", "/"} -_QUERY_CHARS = _FRAGMENT_CHARS = _PATH_CHARS | {"?"} +SUB_DELIM_CHARS = set("!$&'()*+,;=") +USERINFO_CHARS = UNRESERVED_CHARS | SUB_DELIM_CHARS | {":"} +PATH_CHARS = USERINFO_CHARS | {"@", "/"} +QUERY_CHARS = FRAGMENT_CHARS = PATH_CHARS | {"?"} -class Url( - typing.NamedTuple( - "Url", - [ - ("scheme", typing.Optional[str]), - ("auth", typing.Optional[str]), - ("host", typing.Optional[str]), - ("port", typing.Optional[int]), - ("path", typing.Optional[str]), - ("query", typing.Optional[str]), - ("fragment", typing.Optional[str]), - ], - ) -): +class Url(namedtuple("Url", url_attrs)): """ Data structure for representing an HTTP URL. Used as a return value for :func:`parse_url`. Both the scheme and host are normalized as they are both case-insensitive according to RFC 3986. """ - def __new__( # type: ignore[no-untyped-def] + __slots__ = () + + def __new__( cls, - scheme: str | None = None, - auth: str | None = None, - host: str | None = None, - port: int | None = None, - path: str | None = None, - query: str | None = None, - fragment: str | None = None, + scheme=None, + auth=None, + host=None, + port=None, + path=None, + query=None, + fragment=None, ): if path and not path.startswith("/"): path = "/" + path if scheme is not None: scheme = scheme.lower() - return super().__new__(cls, scheme, auth, host, port, path, query, fragment) + return super(Url, cls).__new__( + cls, scheme, auth, host, port, path, query, fragment + ) @property - def hostname(self) -> str | None: + def hostname(self): """For backwards-compatibility with urlparse. We're nice like that.""" return self.host @property - def request_uri(self) -> str: + def request_uri(self): """Absolute path including the query string.""" uri = self.path or "/" @@ -129,37 +122,14 @@ class Url( return uri @property - def authority(self) -> str | None: - """ - Authority component as defined in RFC 3986 3.2. - This includes userinfo (auth), host and port. - - i.e. - userinfo@host:port - """ - userinfo = self.auth - netloc = self.netloc - if netloc is None or userinfo is None: - return netloc - else: - return f"{userinfo}@{netloc}" - - @property - def netloc(self) -> str | None: - """ - Network location including host and port. - - If you need the equivalent of urllib.parse's ``netloc``, - use the ``authority`` property instead. - """ - if self.host is None: - return None + def netloc(self): + """Network location including host and port""" if self.port: - return f"{self.host}:{self.port}" + return "%s:%d" % (self.host, self.port) return self.host @property - def url(self) -> str: + def url(self): """ Convert self into a url @@ -168,77 +138,88 @@ class Url( :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls with a blank port will have : removed). - Example: + Example: :: - .. code-block:: python - - import urllib3 - - U = urllib3.util.parse_url("https://google.com/mail/") - - print(U.url) - # "https://google.com/mail/" - - print( urllib3.util.Url("https", "username:password", - "host.com", 80, "/path", "query", "fragment" - ).url - ) - # "https://username:password@host.com:80/path?query#fragment" + >>> U = parse_url('http://google.com/mail/') + >>> U.url + 'http://google.com/mail/' + >>> Url('http', 'username:password', 'host.com', 80, + ... '/path', 'query', 'fragment').url + 'http://username:password@host.com:80/path?query#fragment' """ scheme, auth, host, port, path, query, fragment = self - url = "" + url = u"" # We use "is not None" we want things to happen with empty strings (or 0 port) if scheme is not None: - url += scheme + "://" + url += scheme + u"://" if auth is not None: - url += auth + "@" + url += auth + u"@" if host is not None: url += host if port is not None: - url += ":" + str(port) + url += u":" + str(port) if path is not None: url += path if query is not None: - url += "?" + query + url += u"?" + query if fragment is not None: - url += "#" + fragment + url += u"#" + fragment return url - def __str__(self) -> str: + def __str__(self): return self.url -@typing.overload -def _encode_invalid_chars( - component: str, allowed_chars: typing.Container[str] -) -> str: # Abstract - ... +def split_first(s, delims): + """ + .. deprecated:: 1.25 + + Given a string and an iterable of delimiters, split on the first found + delimiter. Return two split parts and the matched delimiter. + + If not found, then the first part is the full input string. + + Example:: + + >>> split_first('foo/bar?baz', '?/=') + ('foo', 'bar?baz', '/') + >>> split_first('foo/bar?baz', '123') + ('foo/bar?baz', '', None) + + Scales linearly with number of delims. Not ideal for large number of delims. + """ + min_idx = None + min_delim = None + for d in delims: + idx = s.find(d) + if idx < 0: + continue + + if min_idx is None or idx < min_idx: + min_idx = idx + min_delim = d + + if min_idx is None or min_idx < 0: + return s, "", None + + return s[:min_idx], s[min_idx + 1 :], min_delim -@typing.overload -def _encode_invalid_chars( - component: None, allowed_chars: typing.Container[str] -) -> None: # Abstract - ... - - -def _encode_invalid_chars( - component: str | None, allowed_chars: typing.Container[str] -) -> str | None: +def _encode_invalid_chars(component, allowed_chars, encoding="utf-8"): """Percent-encodes a URI component without reapplying onto an already percent-encoded component. """ if component is None: return component - component = to_str(component) + component = six.ensure_text(component) # Normalize existing percent-encoded bytes. # Try to see if the component we're encoding is already percent-encoded # so we can skip all '%' characters but still encode all others. - component, percent_encodings = _PERCENT_RE.subn( + component, percent_encodings = PERCENT_RE.subn( lambda match: match.group(0).upper(), component ) @@ -247,7 +228,7 @@ def _encode_invalid_chars( encoded_component = bytearray() for i in range(0, len(uri_bytes)): - # Will return a single character bytestring + # Will return a single character bytestring on both Python 2 & 3 byte = uri_bytes[i : i + 1] byte_ord = ord(byte) if (is_percent_encoded and byte == b"%") or ( @@ -257,10 +238,10 @@ def _encode_invalid_chars( continue encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper())) - return encoded_component.decode() + return encoded_component.decode(encoding) -def _remove_path_dot_segments(path: str) -> str: +def _remove_path_dot_segments(path): # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code segments = path.split("/") # Turn the path into a list of segments output = [] # Initialize the variable to use to store output @@ -270,7 +251,7 @@ def _remove_path_dot_segments(path: str) -> str: if segment == ".": continue # Anything other than '..', should be appended to the output - if segment != "..": + elif segment != "..": output.append(segment) # In this case segment == '..', if we can, we should pop the last # element @@ -290,23 +271,18 @@ def _remove_path_dot_segments(path: str) -> str: return "/".join(output) -@typing.overload -def _normalize_host(host: None, scheme: str | None) -> None: ... - - -@typing.overload -def _normalize_host(host: str, scheme: str | None) -> str: ... - - -def _normalize_host(host: str | None, scheme: str | None) -> str | None: +def _normalize_host(host, scheme): if host: - if scheme in _NORMALIZABLE_SCHEMES: - is_ipv6 = _IPV6_ADDRZ_RE.match(host) + if isinstance(host, six.binary_type): + host = six.ensure_str(host) + + if scheme in NORMALIZABLE_SCHEMES: + is_ipv6 = IPV6_ADDRZ_RE.match(host) if is_ipv6: # IPv6 hosts of the form 'a::b%zone' are encoded in a URL as # such per RFC 6874: 'a::b%25zone'. Unquote the ZoneID # separator as necessary to return a valid RFC 4007 scoped IP. - match = _ZONE_ID_RE.search(host) + match = ZONE_ID_RE.search(host) if match: start, end = match.span(1) zone_id = host[start:end] @@ -315,56 +291,46 @@ def _normalize_host(host: str | None, scheme: str | None) -> str | None: zone_id = zone_id[3:] else: zone_id = zone_id[1:] - zone_id = _encode_invalid_chars(zone_id, _UNRESERVED_CHARS) - return f"{host[:start].lower()}%{zone_id}{host[end:]}" + zone_id = "%" + _encode_invalid_chars(zone_id, UNRESERVED_CHARS) + return host[:start].lower() + zone_id + host[end:] else: return host.lower() - elif not _IPV4_RE.match(host): - return to_str( - b".".join([_idna_encode(label) for label in host.split(".")]), - "ascii", + elif not IPV4_RE.match(host): + return six.ensure_str( + b".".join([_idna_encode(label) for label in host.split(".")]) ) return host -def _idna_encode(name: str) -> bytes: - if not name.isascii(): +def _idna_encode(name): + if name and any(ord(x) >= 128 for x in name): try: import idna except ImportError: - raise LocationParseError( - "Unable to parse URL without the 'idna' module" - ) from None - + six.raise_from( + LocationParseError("Unable to parse URL without the 'idna' module"), + None, + ) try: return idna.encode(name.lower(), strict=True, std3_rules=True) except idna.IDNAError: - raise LocationParseError( - f"Name '{name}' is not a valid IDNA label" - ) from None - + six.raise_from( + LocationParseError(u"Name '%s' is not a valid IDNA label" % name), None + ) return name.lower().encode("ascii") -def _encode_target(target: str) -> str: - """Percent-encodes a request target so that there are no invalid characters - - Pre-condition for this function is that 'target' must start with '/'. - If that is the case then _TARGET_RE will always produce a match. - """ - match = _TARGET_RE.match(target) - if not match: # Defensive: - raise LocationParseError(f"{target!r} is not a valid request URI") - - path, query = match.groups() - encoded_target = _encode_invalid_chars(path, _PATH_CHARS) +def _encode_target(target): + """Percent-encodes a request target so that there are no invalid characters""" + path, query = TARGET_RE.match(target).groups() + target = _encode_invalid_chars(path, PATH_CHARS) + query = _encode_invalid_chars(query, QUERY_CHARS) if query is not None: - query = _encode_invalid_chars(query, _QUERY_CHARS) - encoded_target += "?" + query - return encoded_target + target += "?" + query + return target -def parse_url(url: str) -> Url: +def parse_url(url): """ Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is performed to parse incomplete urls. Fields not provided will be None. @@ -375,44 +341,28 @@ def parse_url(url: str) -> Url: :param str url: URL to parse into a :class:`.Url` namedtuple. - Partly backwards-compatible with :mod:`urllib.parse`. + Partly backwards-compatible with :mod:`urlparse`. - Example: + Example:: - .. code-block:: python - - import urllib3 - - print( urllib3.util.parse_url('http://google.com/mail/')) - # Url(scheme='http', host='google.com', port=None, path='/mail/', ...) - - print( urllib3.util.parse_url('google.com:80')) - # Url(scheme=None, host='google.com', port=80, path=None, ...) - - print( urllib3.util.parse_url('/foo?bar')) - # Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + >>> parse_url('http://google.com/mail/') + Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + >>> parse_url('google.com:80') + Url(scheme=None, host='google.com', port=80, path=None, ...) + >>> parse_url('/foo?bar') + Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) """ if not url: # Empty return Url() source_url = url - if not _SCHEME_RE.search(url): + if not SCHEME_RE.search(url): url = "//" + url - scheme: str | None - authority: str | None - auth: str | None - host: str | None - port: str | None - port_int: int | None - path: str | None - query: str | None - fragment: str | None - try: - scheme, authority, path, query, fragment = _URI_RE.match(url).groups() # type: ignore[union-attr] - normalize_uri = scheme is None or scheme.lower() in _NORMALIZABLE_SCHEMES + scheme, authority, path, query, fragment = URI_RE.match(url).groups() + normalize_uri = scheme is None or scheme.lower() in NORMALIZABLE_SCHEMES if scheme: scheme = scheme.lower() @@ -420,33 +370,31 @@ def parse_url(url: str) -> Url: if authority: auth, _, host_port = authority.rpartition("@") auth = auth or None - host, port = _HOST_PORT_RE.match(host_port).groups() # type: ignore[union-attr] + host, port = _HOST_PORT_RE.match(host_port).groups() if auth and normalize_uri: - auth = _encode_invalid_chars(auth, _USERINFO_CHARS) + auth = _encode_invalid_chars(auth, USERINFO_CHARS) if port == "": port = None else: auth, host, port = None, None, None if port is not None: - port_int = int(port) - if not (0 <= port_int <= 65535): + port = int(port) + if not (0 <= port <= 65535): raise LocationParseError(url) - else: - port_int = None host = _normalize_host(host, scheme) if normalize_uri and path: path = _remove_path_dot_segments(path) - path = _encode_invalid_chars(path, _PATH_CHARS) + path = _encode_invalid_chars(path, PATH_CHARS) if normalize_uri and query: - query = _encode_invalid_chars(query, _QUERY_CHARS) + query = _encode_invalid_chars(query, QUERY_CHARS) if normalize_uri and fragment: - fragment = _encode_invalid_chars(fragment, _FRAGMENT_CHARS) + fragment = _encode_invalid_chars(fragment, FRAGMENT_CHARS) - except (ValueError, AttributeError) as e: - raise LocationParseError(source_url) from e + except (ValueError, AttributeError): + return six.raise_from(LocationParseError(source_url), None) # For the sake of backwards compatibility we put empty # string values for path if there are any defined values @@ -458,12 +406,30 @@ def parse_url(url: str) -> Url: else: path = None + # Ensure that each part of the URL is a `str` for + # backwards compatibility. + if isinstance(url, six.text_type): + ensure_func = six.ensure_text + else: + ensure_func = six.ensure_str + + def ensure_type(x): + return x if x is None else ensure_func(x) + return Url( - scheme=scheme, - auth=auth, - host=host, - port=port_int, - path=path, - query=query, - fragment=fragment, + scheme=ensure_type(scheme), + auth=ensure_type(auth), + host=ensure_type(host), + port=port, + path=ensure_type(path), + query=ensure_type(query), + fragment=ensure_type(fragment), ) + + +def get_host(url): + """ + Deprecated. Use :func:`parse_url` instead. + """ + p = parse_url(url) + return p.scheme or "http", p.hostname, p.port diff --git a/venv/lib/python3.12/site-packages/urllib3/util/util.py b/venv/lib/python3.12/site-packages/urllib3/util/util.py deleted file mode 100644 index 35c77e4..0000000 --- a/venv/lib/python3.12/site-packages/urllib3/util/util.py +++ /dev/null @@ -1,42 +0,0 @@ -from __future__ import annotations - -import typing -from types import TracebackType - - -def to_bytes( - x: str | bytes, encoding: str | None = None, errors: str | None = None -) -> bytes: - if isinstance(x, bytes): - return x - elif not isinstance(x, str): - raise TypeError(f"not expecting type {type(x).__name__}") - if encoding or errors: - return x.encode(encoding or "utf-8", errors=errors or "strict") - return x.encode() - - -def to_str( - x: str | bytes, encoding: str | None = None, errors: str | None = None -) -> str: - if isinstance(x, str): - return x - elif not isinstance(x, bytes): - raise TypeError(f"not expecting type {type(x).__name__}") - if encoding or errors: - return x.decode(encoding or "utf-8", errors=errors or "strict") - return x.decode() - - -def reraise( - tp: type[BaseException] | None, - value: BaseException, - tb: TracebackType | None = None, -) -> typing.NoReturn: - try: - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - finally: - value = None # type: ignore[assignment] - tb = None diff --git a/venv/lib/python3.12/site-packages/urllib3/util/wait.py b/venv/lib/python3.12/site-packages/urllib3/util/wait.py index aeca0c7..21b4590 100644 --- a/venv/lib/python3.12/site-packages/urllib3/util/wait.py +++ b/venv/lib/python3.12/site-packages/urllib3/util/wait.py @@ -1,10 +1,18 @@ -from __future__ import annotations - +import errno import select -import socket +import sys from functools import partial -__all__ = ["wait_for_read", "wait_for_write"] +try: + from time import monotonic +except ImportError: + from time import time as monotonic + +__all__ = ["NoWayToWaitForSocketError", "wait_for_read", "wait_for_write"] + + +class NoWayToWaitForSocketError(Exception): + pass # How should we wait on sockets? @@ -29,13 +37,37 @@ __all__ = ["wait_for_read", "wait_for_write"] # So: on Windows we use select(), and everywhere else we use poll(). We also # fall back to select() in case poll() is somehow broken or missing. +if sys.version_info >= (3, 5): + # Modern Python, that retries syscalls by default + def _retry_on_intr(fn, timeout): + return fn(timeout) -def select_wait_for_socket( - sock: socket.socket, - read: bool = False, - write: bool = False, - timeout: float | None = None, -) -> bool: +else: + # Old and broken Pythons. + def _retry_on_intr(fn, timeout): + if timeout is None: + deadline = float("inf") + else: + deadline = monotonic() + timeout + + while True: + try: + return fn(timeout) + # OSError for 3 <= pyver < 3.5, select.error for pyver <= 2.7 + except (OSError, select.error) as e: + # 'e.args[0]' incantation works for both OSError and select.error + if e.args[0] != errno.EINTR: + raise + else: + timeout = deadline - monotonic() + if timeout < 0: + timeout = 0 + if timeout == float("inf"): + timeout = None + continue + + +def select_wait_for_socket(sock, read=False, write=False, timeout=None): if not read and not write: raise RuntimeError("must specify at least one of read=True, write=True") rcheck = [] @@ -50,16 +82,11 @@ def select_wait_for_socket( # sockets for both conditions. (The stdlib selectors module does the same # thing.) fn = partial(select.select, rcheck, wcheck, wcheck) - rready, wready, xready = fn(timeout) + rready, wready, xready = _retry_on_intr(fn, timeout) return bool(rready or wready or xready) -def poll_wait_for_socket( - sock: socket.socket, - read: bool = False, - write: bool = False, - timeout: float | None = None, -) -> bool: +def poll_wait_for_socket(sock, read=False, write=False, timeout=None): if not read and not write: raise RuntimeError("must specify at least one of read=True, write=True") mask = 0 @@ -71,33 +98,32 @@ def poll_wait_for_socket( poll_obj.register(sock, mask) # For some reason, poll() takes timeout in milliseconds - def do_poll(t: float | None) -> list[tuple[int, int]]: + def do_poll(t): if t is not None: t *= 1000 return poll_obj.poll(t) - return bool(do_poll(timeout)) + return bool(_retry_on_intr(do_poll, timeout)) -def _have_working_poll() -> bool: +def null_wait_for_socket(*args, **kwargs): + raise NoWayToWaitForSocketError("no select-equivalent available") + + +def _have_working_poll(): # Apparently some systems have a select.poll that fails as soon as you try # to use it, either due to strange configuration or broken monkeypatching # from libraries like eventlet/greenlet. try: poll_obj = select.poll() - poll_obj.poll(0) + _retry_on_intr(poll_obj.poll, 0) except (AttributeError, OSError): return False else: return True -def wait_for_socket( - sock: socket.socket, - read: bool = False, - write: bool = False, - timeout: float | None = None, -) -> bool: +def wait_for_socket(*args, **kwargs): # We delay choosing which implementation to use until the first time we're # called. We could do it at import time, but then we might make the wrong # decision if someone goes wild with monkeypatching select.poll after @@ -107,17 +133,19 @@ def wait_for_socket( wait_for_socket = poll_wait_for_socket elif hasattr(select, "select"): wait_for_socket = select_wait_for_socket - return wait_for_socket(sock, read, write, timeout) + else: # Platform-specific: Appengine. + wait_for_socket = null_wait_for_socket + return wait_for_socket(*args, **kwargs) -def wait_for_read(sock: socket.socket, timeout: float | None = None) -> bool: +def wait_for_read(sock, timeout=None): """Waits for reading to be available on a given socket. Returns True if the socket is readable, or False if the timeout expired. """ return wait_for_socket(sock, read=True, timeout=timeout) -def wait_for_write(sock: socket.socket, timeout: float | None = None) -> bool: +def wait_for_write(sock, timeout=None): """Waits for writing to be available on a given socket. Returns True if the socket is readable, or False if the timeout expired. """

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 literal 57709 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 IZ&(`kzjhtWkN^Mx diff --git a/venv/lib/python3.12/site-packages/urllib3/_base_connection.py b/venv/lib/python3.12/site-packages/urllib3/_base_connection.py deleted file mode 100644 index 992ec16..0000000 --- a/venv/lib/python3.12/site-packages/urllib3/_base_connection.py +++ /dev/null @@ -1,167 +0,0 @@ -from __future__ import annotations - -import typing - -from .util.connection import _TYPE_SOCKET_OPTIONS -from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT -from .util.url import Url - -_TYPE_BODY = typing.Union[ - bytes, typing.IO[typing.Any], typing.Iterable[bytes | str], str -] - - -class ProxyConfig(typing.NamedTuple): - ssl_context: ssl.SSLContext | None - use_forwarding_for_https: bool - assert_hostname: None | str | typing.Literal[False] - assert_fingerprint: str | None - - -class _ResponseOptions(typing.NamedTuple): - # TODO: Remove this in favor of a better - # HTTP request/response lifecycle tracking. - request_method: str - request_url: str - preload_content: bool - decode_content: bool - enforce_content_length: bool - - -if typing.TYPE_CHECKING: - import ssl - from typing import Protocol - - from .response import BaseHTTPResponse - - class BaseHTTPConnection(Protocol): - default_port: typing.ClassVar[int] - default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] - - host: str - port: int - timeout: None | ( - float - ) # Instance doesn't store _DEFAULT_TIMEOUT, must be resolved. - blocksize: int - source_address: tuple[str, int] | None - socket_options: _TYPE_SOCKET_OPTIONS | None - - proxy: Url | None - proxy_config: ProxyConfig | None - - is_verified: bool - proxy_is_verified: bool | None - - def __init__( - self, - host: str, - port: int | None = None, - *, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - blocksize: int = 8192, - socket_options: _TYPE_SOCKET_OPTIONS | None = ..., - proxy: Url | None = None, - proxy_config: ProxyConfig | None = None, - ) -> None: ... - - def set_tunnel( - self, - host: str, - port: int | None = None, - headers: typing.Mapping[str, str] | None = None, - scheme: str = "http", - ) -> None: ... - - def connect(self) -> None: ... - - def request( - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - # We know *at least* botocore is depending on the order of the - # first 3 parameters so to be safe we only mark the later ones - # as keyword-only to ensure we have space to extend. - *, - chunked: bool = False, - preload_content: bool = True, - decode_content: bool = True, - enforce_content_length: bool = True, - ) -> None: ... - - def getresponse(self) -> BaseHTTPResponse: ... - - def close(self) -> None: ... - - @property - def is_closed(self) -> bool: - """Whether the connection either is brand new or has been previously closed. - If this property is True then both ``is_connected`` and ``has_connected_to_proxy`` - properties must be False. - """ - - @property - def is_connected(self) -> bool: - """Whether the connection is actively connected to any origin (proxy or target)""" - - @property - def has_connected_to_proxy(self) -> bool: - """Whether the connection has successfully connected to its proxy. - This returns False if no proxy is in use. Used to determine whether - errors are coming from the proxy layer or from tunnelling to the target origin. - """ - - class BaseHTTPSConnection(BaseHTTPConnection, Protocol): - default_port: typing.ClassVar[int] - default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] - - # Certificate verification methods - cert_reqs: int | str | None - assert_hostname: None | str | typing.Literal[False] - assert_fingerprint: str | None - ssl_context: ssl.SSLContext | None - - # Trusted CAs - ca_certs: str | None - ca_cert_dir: str | None - ca_cert_data: None | str | bytes - - # TLS version - ssl_minimum_version: int | None - ssl_maximum_version: int | None - ssl_version: int | str | None # Deprecated - - # Client certificates - cert_file: str | None - key_file: str | None - key_password: str | None - - def __init__( - self, - host: str, - port: int | None = None, - *, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - blocksize: int = 16384, - socket_options: _TYPE_SOCKET_OPTIONS | None = ..., - proxy: Url | None = None, - proxy_config: ProxyConfig | None = None, - cert_reqs: int | str | None = None, - assert_hostname: None | str | typing.Literal[False] = None, - assert_fingerprint: str | None = None, - server_hostname: str | None = None, - ssl_context: ssl.SSLContext | None = None, - ca_certs: str | None = None, - ca_cert_dir: str | None = None, - ca_cert_data: None | str | bytes = None, - ssl_minimum_version: int | None = None, - ssl_maximum_version: int | None = None, - ssl_version: int | str | None = None, # Deprecated - cert_file: str | None = None, - key_file: str | None = None, - key_password: str | None = None, - ) -> None: ... diff --git a/venv/lib/python3.12/site-packages/urllib3/_collections.py b/venv/lib/python3.12/site-packages/urllib3/_collections.py index ee9ca66..bceb845 100644 --- a/venv/lib/python3.12/site-packages/urllib3/_collections.py +++ b/venv/lib/python3.12/site-packages/urllib3/_collections.py @@ -1,66 +1,34 @@ -from __future__ import annotations +from __future__ import absolute_import + +try: + from collections.abc import Mapping, MutableMapping +except ImportError: + from collections import Mapping, MutableMapping +try: + from threading import RLock +except ImportError: # Platform-specific: No threads available + + class RLock: + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + pass + -import typing from collections import OrderedDict -from enum import Enum, auto -from threading import RLock - -if typing.TYPE_CHECKING: - # We can only import Protocol if TYPE_CHECKING because it's a development - # dependency, and is not available at runtime. - from typing import Protocol - - from typing_extensions import Self - - class HasGettableStringKeys(Protocol): - def keys(self) -> typing.Iterator[str]: ... - - def __getitem__(self, key: str) -> str: ... +from .exceptions import InvalidHeader +from .packages import six +from .packages.six import iterkeys, itervalues __all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] -# Key type -_KT = typing.TypeVar("_KT") -# Value type -_VT = typing.TypeVar("_VT") -# Default type -_DT = typing.TypeVar("_DT") - -ValidHTTPHeaderSource = typing.Union[ - "HTTPHeaderDict", - typing.Mapping[str, str], - typing.Iterable[tuple[str, str]], - "HasGettableStringKeys", -] +_Null = object() -class _Sentinel(Enum): - not_passed = auto() - - -def ensure_can_construct_http_header_dict( - potential: object, -) -> ValidHTTPHeaderSource | None: - if isinstance(potential, HTTPHeaderDict): - return potential - elif isinstance(potential, typing.Mapping): - # Full runtime checking of the contents of a Mapping is expensive, so for the - # purposes of typechecking, we assume that any Mapping is the right shape. - return typing.cast(typing.Mapping[str, str], potential) - elif isinstance(potential, typing.Iterable): - # Similarly to Mapping, full runtime checking of the contents of an Iterable is - # expensive, so for the purposes of typechecking, we assume that any Iterable - # is the right shape. - return typing.cast(typing.Iterable[tuple[str, str]], potential) - elif hasattr(potential, "keys") and hasattr(potential, "__getitem__"): - return typing.cast("HasGettableStringKeys", potential) - else: - return None - - -class RecentlyUsedContainer(typing.Generic[_KT, _VT], typing.MutableMapping[_KT, _VT]): +class RecentlyUsedContainer(MutableMapping): """ Provides a thread-safe dict-like container which maintains up to ``maxsize`` keys while throwing away the least-recently-used keys beyond @@ -74,134 +42,69 @@ class RecentlyUsedContainer(typing.Generic[_KT, _VT], typing.MutableMapping[_KT, ``dispose_func(value)`` is called. Callback which will get called """ - _container: typing.OrderedDict[_KT, _VT] - _maxsize: int - dispose_func: typing.Callable[[_VT], None] | None - lock: RLock + ContainerCls = OrderedDict - def __init__( - self, - maxsize: int = 10, - dispose_func: typing.Callable[[_VT], None] | None = None, - ) -> None: - super().__init__() + def __init__(self, maxsize=10, dispose_func=None): self._maxsize = maxsize self.dispose_func = dispose_func - self._container = OrderedDict() + + self._container = self.ContainerCls() self.lock = RLock() - def __getitem__(self, key: _KT) -> _VT: + def __getitem__(self, key): # Re-insert the item, moving it to the end of the eviction line. with self.lock: item = self._container.pop(key) self._container[key] = item return item - def __setitem__(self, key: _KT, value: _VT) -> None: - evicted_item = None + def __setitem__(self, key, value): + evicted_value = _Null with self.lock: # Possibly evict the existing value of 'key' - try: - # If the key exists, we'll overwrite it, which won't change the - # size of the pool. Because accessing a key should move it to - # the end of the eviction line, we pop it out first. - evicted_item = key, self._container.pop(key) - self._container[key] = value - except KeyError: - # When the key does not exist, we insert the value first so that - # evicting works in all cases, including when self._maxsize is 0 - self._container[key] = value - if len(self._container) > self._maxsize: - # If we didn't evict an existing value, and we've hit our maximum - # size, then we have to evict the least recently used item from - # the beginning of the container. - evicted_item = self._container.popitem(last=False) + evicted_value = self._container.get(key, _Null) + self._container[key] = value - # After releasing the lock on the pool, dispose of any evicted value. - if evicted_item is not None and self.dispose_func: - _, evicted_value = evicted_item + # If we didn't evict an existing value, we might have to evict the + # least recently used item from the beginning of the container. + if len(self._container) > self._maxsize: + _key, evicted_value = self._container.popitem(last=False) + + if self.dispose_func and evicted_value is not _Null: self.dispose_func(evicted_value) - def __delitem__(self, key: _KT) -> None: + def __delitem__(self, key): with self.lock: value = self._container.pop(key) if self.dispose_func: self.dispose_func(value) - def __len__(self) -> int: + def __len__(self): with self.lock: return len(self._container) - def __iter__(self) -> typing.NoReturn: + def __iter__(self): raise NotImplementedError( "Iteration over this class is unlikely to be threadsafe." ) - def clear(self) -> None: + def clear(self): with self.lock: # Copy pointers to all values, then wipe the mapping - values = list(self._container.values()) + values = list(itervalues(self._container)) self._container.clear() if self.dispose_func: for value in values: self.dispose_func(value) - def keys(self) -> set[_KT]: # type: ignore[override] + def keys(self): with self.lock: - return set(self._container.keys()) + return list(iterkeys(self._container)) -class HTTPHeaderDictItemView(set[tuple[str, str]]): - """ - HTTPHeaderDict is unusual for a Mapping[str, str] in that it has two modes of - address. - - If we directly try to get an item with a particular name, we will get a string - back that is the concatenated version of all the values: - - >>> d['X-Header-Name'] - 'Value1, Value2, Value3' - - However, if we iterate over an HTTPHeaderDict's items, we will optionally combine - these values based on whether combine=True was called when building up the dictionary - - >>> d = HTTPHeaderDict({"A": "1", "B": "foo"}) - >>> d.add("A", "2", combine=True) - >>> d.add("B", "bar") - >>> list(d.items()) - [ - ('A', '1, 2'), - ('B', 'foo'), - ('B', 'bar'), - ] - - This class conforms to the interface required by the MutableMapping ABC while - also giving us the nonstandard iteration behavior we want; items with duplicate - keys, ordered by time of first insertion. - """ - - _headers: HTTPHeaderDict - - def __init__(self, headers: HTTPHeaderDict) -> None: - self._headers = headers - - def __len__(self) -> int: - return len(list(self._headers.iteritems())) - - def __iter__(self) -> typing.Iterator[tuple[str, str]]: - return self._headers.iteritems() - - def __contains__(self, item: object) -> bool: - if isinstance(item, tuple) and len(item) == 2: - passed_key, passed_val = item - if isinstance(passed_key, str) and isinstance(passed_val, str): - return self._headers._has_value_for_header(passed_key, passed_val) - return False - - -class HTTPHeaderDict(typing.MutableMapping[str, str]): +class HTTPHeaderDict(MutableMapping): """ :param headers: An iterable of field-value pairs. Must not contain multiple field names @@ -235,11 +138,9 @@ class HTTPHeaderDict(typing.MutableMapping[str, str]): '7' """ - _container: typing.MutableMapping[str, list[str]] - - def __init__(self, headers: ValidHTTPHeaderSource | None = None, **kwargs: str): - super().__init__() - self._container = {} # 'dict' is insert-ordered + def __init__(self, headers=None, **kwargs): + super(HTTPHeaderDict, self).__init__() + self._container = OrderedDict() if headers is not None: if isinstance(headers, HTTPHeaderDict): self._copy_from(headers) @@ -248,155 +149,126 @@ class HTTPHeaderDict(typing.MutableMapping[str, str]): if kwargs: self.extend(kwargs) - def __setitem__(self, key: str, val: str) -> None: - # avoid a bytes/str comparison by decoding before httplib - if isinstance(key, bytes): - key = key.decode("latin-1") + def __setitem__(self, key, val): self._container[key.lower()] = [key, val] + return self._container[key.lower()] - def __getitem__(self, key: str) -> str: - if isinstance(key, bytes): - key = key.decode("latin-1") + def __getitem__(self, key): val = self._container[key.lower()] return ", ".join(val[1:]) - def __delitem__(self, key: str) -> None: - if isinstance(key, bytes): - key = key.decode("latin-1") + def __delitem__(self, key): del self._container[key.lower()] - def __contains__(self, key: object) -> bool: - if isinstance(key, bytes): - key = key.decode("latin-1") - if isinstance(key, str): - return key.lower() in self._container - return False + def __contains__(self, key): + return key.lower() in self._container - def setdefault(self, key: str, default: str = "") -> str: - return super().setdefault(key, default) - - def __eq__(self, other: object) -> bool: - maybe_constructable = ensure_can_construct_http_header_dict(other) - if maybe_constructable is None: + def __eq__(self, other): + if not isinstance(other, Mapping) and not hasattr(other, "keys"): return False - else: - other_as_http_header_dict = type(self)(maybe_constructable) + if not isinstance(other, type(self)): + other = type(self)(other) + return dict((k.lower(), v) for k, v in self.itermerged()) == dict( + (k.lower(), v) for k, v in other.itermerged() + ) - return {k.lower(): v for k, v in self.itermerged()} == { - k.lower(): v for k, v in other_as_http_header_dict.itermerged() - } - - def __ne__(self, other: object) -> bool: + def __ne__(self, other): return not self.__eq__(other) - def __len__(self) -> int: + if six.PY2: # Python 2 + iterkeys = MutableMapping.iterkeys + itervalues = MutableMapping.itervalues + + __marker = object() + + def __len__(self): return len(self._container) - def __iter__(self) -> typing.Iterator[str]: + def __iter__(self): # Only provide the originally cased names for vals in self._container.values(): yield vals[0] - def discard(self, key: str) -> None: + def pop(self, key, default=__marker): + """D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + """ + # Using the MutableMapping function directly fails due to the private marker. + # Using ordinary dict.pop would expose the internal structures. + # So let's reinvent the wheel. + try: + value = self[key] + except KeyError: + if default is self.__marker: + raise + return default + else: + del self[key] + return value + + def discard(self, key): try: del self[key] except KeyError: pass - def add(self, key: str, val: str, *, combine: bool = False) -> None: + def add(self, key, val): """Adds a (name, value) pair, doesn't overwrite the value if it already exists. - If this is called with combine=True, instead of adding a new header value - as a distinct item during iteration, this will instead append the value to - any existing header value with a comma. If no existing header value exists - for the key, then the value will simply be added, ignoring the combine parameter. - >>> headers = HTTPHeaderDict(foo='bar') >>> headers.add('Foo', 'baz') >>> headers['foo'] 'bar, baz' - >>> list(headers.items()) - [('foo', 'bar'), ('foo', 'baz')] - >>> headers.add('foo', 'quz', combine=True) - >>> list(headers.items()) - [('foo', 'bar, baz, quz')] """ - # avoid a bytes/str comparison by decoding before httplib - if isinstance(key, bytes): - key = key.decode("latin-1") key_lower = key.lower() new_vals = [key, val] # Keep the common case aka no item present as fast as possible vals = self._container.setdefault(key_lower, new_vals) if new_vals is not vals: - # if there are values here, then there is at least the initial - # key/value pair - assert len(vals) >= 2 - if combine: - vals[-1] = vals[-1] + ", " + val - else: - vals.append(val) + vals.append(val) - def extend(self, *args: ValidHTTPHeaderSource, **kwargs: str) -> None: + def extend(self, *args, **kwargs): """Generic import function for any type of header-like object. Adapted version of MutableMapping.update in order to insert items with self.add instead of self.__setitem__ """ if len(args) > 1: raise TypeError( - f"extend() takes at most 1 positional arguments ({len(args)} given)" + "extend() takes at most 1 positional " + "arguments ({0} given)".format(len(args)) ) other = args[0] if len(args) >= 1 else () if isinstance(other, HTTPHeaderDict): for key, val in other.iteritems(): self.add(key, val) - elif isinstance(other, typing.Mapping): - for key, val in other.items(): - self.add(key, val) - elif isinstance(other, typing.Iterable): - for key, value in other: - self.add(key, value) - elif hasattr(other, "keys") and hasattr(other, "__getitem__"): - # THIS IS NOT A TYPESAFE BRANCH - # In this branch, the object has a `keys` attr but is not a Mapping or any of - # the other types indicated in the method signature. We do some stuff with - # it as though it partially implements the Mapping interface, but we're not - # doing that stuff safely AT ALL. + elif isinstance(other, Mapping): + for key in other: + self.add(key, other[key]) + elif hasattr(other, "keys"): for key in other.keys(): self.add(key, other[key]) + else: + for key, value in other: + self.add(key, value) for key, value in kwargs.items(): self.add(key, value) - @typing.overload - def getlist(self, key: str) -> list[str]: ... - - @typing.overload - def getlist(self, key: str, default: _DT) -> list[str] | _DT: ... - - def getlist( - self, key: str, default: _Sentinel | _DT = _Sentinel.not_passed - ) -> list[str] | _DT: + def getlist(self, key, default=__marker): """Returns a list of all the values for the named field. Returns an empty list if the key doesn't exist.""" - if isinstance(key, bytes): - key = key.decode("latin-1") try: vals = self._container[key.lower()] except KeyError: - if default is _Sentinel.not_passed: - # _DT is unbound; empty list is instance of List[str] + if default is self.__marker: return [] - # _DT is bound; default is instance of _DT return default else: - # _DT may or may not be bound; vals[1:] is instance of List[str], which - # meets our external interface requirement of `Union[List[str], _DT]`. return vals[1:] - def _prepare_for_method_change(self) -> Self: + def _prepare_for_method_change(self): """ Remove content-specific header fields before changing the request method to GET or HEAD according to RFC 9110, Section 15.4. @@ -422,65 +294,62 @@ class HTTPHeaderDict(typing.MutableMapping[str, str]): # Backwards compatibility for http.cookiejar get_all = getlist - def __repr__(self) -> str: - return f"{type(self).__name__}({dict(self.itermerged())})" + def __repr__(self): + return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) - def _copy_from(self, other: HTTPHeaderDict) -> None: + def _copy_from(self, other): for key in other: val = other.getlist(key) - self._container[key.lower()] = [key, *val] + if isinstance(val, list): + # Don't need to convert tuples + val = list(val) + self._container[key.lower()] = [key] + val - def copy(self) -> Self: + def copy(self): clone = type(self)() clone._copy_from(self) return clone - def iteritems(self) -> typing.Iterator[tuple[str, str]]: + def iteritems(self): """Iterate over all header lines, including duplicate ones.""" for key in self: vals = self._container[key.lower()] for val in vals[1:]: yield vals[0], val - def itermerged(self) -> typing.Iterator[tuple[str, str]]: + def itermerged(self): """Iterate over all headers, merging duplicate ones together.""" for key in self: val = self._container[key.lower()] yield val[0], ", ".join(val[1:]) - def items(self) -> HTTPHeaderDictItemView: # type: ignore[override] - return HTTPHeaderDictItemView(self) + def items(self): + return list(self.iteritems()) - def _has_value_for_header(self, header_name: str, potential_value: str) -> bool: - if header_name in self: - return potential_value in self._container[header_name.lower()][1:] - return False + @classmethod + def from_httplib(cls, message): # Python 2 + """Read headers from a Python 2 httplib message object.""" + # python2.7 does not expose a proper API for exporting multiheaders + # efficiently. This function re-reads raw lines from the message + # object and extracts the multiheaders properly. + obs_fold_continued_leaders = (" ", "\t") + headers = [] - def __ior__(self, other: object) -> HTTPHeaderDict: - # Supports extending a header dict in-place using operator |= - # combining items with add instead of __setitem__ - maybe_constructable = ensure_can_construct_http_header_dict(other) - if maybe_constructable is None: - return NotImplemented - self.extend(maybe_constructable) - return self + for line in message.headers: + if line.startswith(obs_fold_continued_leaders): + if not headers: + # We received a header line that starts with OWS as described + # in RFC-7230 S3.2.4. This indicates a multiline header, but + # there exists no previous header to which we can attach it. + raise InvalidHeader( + "Header continuation with no previous header: %s" % line + ) + else: + key, value = headers[-1] + headers[-1] = (key, value + " " + line.strip()) + continue - def __or__(self, other: object) -> Self: - # Supports merging header dicts using operator | - # combining items with add instead of __setitem__ - maybe_constructable = ensure_can_construct_http_header_dict(other) - if maybe_constructable is None: - return NotImplemented - result = self.copy() - result.extend(maybe_constructable) - return result + key, value = line.split(":", 1) + headers.append((key, value.strip())) - def __ror__(self, other: object) -> Self: - # Supports merging header dicts using operator | when other is on left side - # combining items with add instead of __setitem__ - maybe_constructable = ensure_can_construct_http_header_dict(other) - if maybe_constructable is None: - return NotImplemented - result = type(self)(maybe_constructable) - result.extend(self) - return result + return cls(headers) diff --git a/venv/lib/python3.12/site-packages/urllib3/_request_methods.py b/venv/lib/python3.12/site-packages/urllib3/_request_methods.py deleted file mode 100644 index 297c271..0000000 --- a/venv/lib/python3.12/site-packages/urllib3/_request_methods.py +++ /dev/null @@ -1,278 +0,0 @@ -from __future__ import annotations - -import json as _json -import typing -from urllib.parse import urlencode - -from ._base_connection import _TYPE_BODY -from ._collections import HTTPHeaderDict -from .filepost import _TYPE_FIELDS, encode_multipart_formdata -from .response import BaseHTTPResponse - -__all__ = ["RequestMethods"] - -_TYPE_ENCODE_URL_FIELDS = typing.Union[ - typing.Sequence[tuple[str, typing.Union[str, bytes]]], - typing.Mapping[str, typing.Union[str, bytes]], -] - - -class RequestMethods: - """ - Convenience mixin for classes who implement a :meth:`urlopen` method, such - as :class:`urllib3.HTTPConnectionPool` and - :class:`urllib3.PoolManager`. - - Provides behavior for making common types of HTTP request methods and - decides which type of request field encoding to use. - - Specifically, - - :meth:`.request_encode_url` is for sending requests whose fields are - encoded in the URL (such as GET, HEAD, DELETE). - - :meth:`.request_encode_body` is for sending requests whose fields are - encoded in the *body* of the request using multipart or www-form-urlencoded - (such as for POST, PUT, PATCH). - - :meth:`.request` is for making any kind of request, it will look up the - appropriate encoding format and use one of the above two methods to make - the request. - - Initializer parameters: - - :param headers: - Headers to include with all requests, unless other headers are given - explicitly. - """ - - _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"} - - def __init__(self, headers: typing.Mapping[str, str] | None = None) -> None: - self.headers = headers or {} - - def urlopen( - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - encode_multipart: bool = True, - multipart_boundary: str | None = None, - **kw: typing.Any, - ) -> BaseHTTPResponse: # Abstract - raise NotImplementedError( - "Classes extending RequestMethods must implement " - "their own ``urlopen`` method." - ) - - def request( - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - fields: _TYPE_FIELDS | None = None, - headers: typing.Mapping[str, str] | None = None, - json: typing.Any | None = None, - **urlopen_kw: typing.Any, - ) -> BaseHTTPResponse: - """ - Make a request using :meth:`urlopen` with the appropriate encoding of - ``fields`` based on the ``method`` used. - - This is a convenience method that requires the least amount of manual - effort. It can be used in most situations, while still having the - option to drop down to more specific methods when necessary, such as - :meth:`request_encode_url`, :meth:`request_encode_body`, - or even the lowest level :meth:`urlopen`. - - :param method: - HTTP request method (such as GET, POST, PUT, etc.) - - :param url: - The URL to perform the request on. - - :param body: - Data to send in the request body, either :class:`str`, :class:`bytes`, - an iterable of :class:`str`/:class:`bytes`, or a file-like object. - - :param fields: - Data to encode and send in the URL or request body, depending on ``method``. - - :param headers: - Dictionary of custom headers to send, such as User-Agent, - If-None-Match, etc. If None, pool headers are used. If provided, - these headers completely replace any pool-specific headers. - - :param json: - Data to encode and send as JSON with UTF-encoded in the request body. - The ``"Content-Type"`` header will be set to ``"application/json"`` - unless specified otherwise. - """ - method = method.upper() - - if json is not None and body is not None: - raise TypeError( - "request got values for both 'body' and 'json' parameters which are mutually exclusive" - ) - - if json is not None: - if headers is None: - headers = self.headers - - if not ("content-type" in map(str.lower, headers.keys())): - headers = HTTPHeaderDict(headers) - headers["Content-Type"] = "application/json" - - body = _json.dumps(json, separators=(",", ":"), ensure_ascii=False).encode( - "utf-8" - ) - - if body is not None: - urlopen_kw["body"] = body - - if method in self._encode_url_methods: - return self.request_encode_url( - method, - url, - fields=fields, # type: ignore[arg-type] - headers=headers, - **urlopen_kw, - ) - else: - return self.request_encode_body( - method, url, fields=fields, headers=headers, **urlopen_kw - ) - - def request_encode_url( - self, - method: str, - url: str, - fields: _TYPE_ENCODE_URL_FIELDS | None = None, - headers: typing.Mapping[str, str] | None = None, - **urlopen_kw: str, - ) -> BaseHTTPResponse: - """ - Make a request using :meth:`urlopen` with the ``fields`` encoded in - the url. This is useful for request methods like GET, HEAD, DELETE, etc. - - :param method: - HTTP request method (such as GET, POST, PUT, etc.) - - :param url: - The URL to perform the request on. - - :param fields: - Data to encode and send in the URL. - - :param headers: - Dictionary of custom headers to send, such as User-Agent, - If-None-Match, etc. If None, pool headers are used. If provided, - these headers completely replace any pool-specific headers. - """ - if headers is None: - headers = self.headers - - extra_kw: dict[str, typing.Any] = {"headers": headers} - extra_kw.update(urlopen_kw) - - if fields: - url += "?" + urlencode(fields) - - return self.urlopen(method, url, **extra_kw) - - def request_encode_body( - self, - method: str, - url: str, - fields: _TYPE_FIELDS | None = None, - headers: typing.Mapping[str, str] | None = None, - encode_multipart: bool = True, - multipart_boundary: str | None = None, - **urlopen_kw: str, - ) -> BaseHTTPResponse: - """ - Make a request using :meth:`urlopen` with the ``fields`` encoded in - the body. This is useful for request methods like POST, PUT, PATCH, etc. - - When ``encode_multipart=True`` (default), then - :func:`urllib3.encode_multipart_formdata` is used to encode - the payload with the appropriate content type. Otherwise - :func:`urllib.parse.urlencode` is used with the - 'application/x-www-form-urlencoded' content type. - - Multipart encoding must be used when posting files, and it's reasonably - safe to use it in other times too. However, it may break request - signing, such as with OAuth. - - Supports an optional ``fields`` parameter of key/value strings AND - key/filetuple. A filetuple is a (filename, data, MIME type) tuple where - the MIME type is optional. For example:: - - fields = { - 'foo': 'bar', - 'fakefile': ('foofile.txt', 'contents of foofile'), - 'realfile': ('barfile.txt', open('realfile').read()), - 'typedfile': ('bazfile.bin', open('bazfile').read(), - 'image/jpeg'), - 'nonamefile': 'contents of nonamefile field', - } - - When uploading a file, providing a filename (the first parameter of the - tuple) is optional but recommended to best mimic behavior of browsers. - - Note that if ``headers`` are supplied, the 'Content-Type' header will - be overwritten because it depends on the dynamic random boundary string - which is used to compose the body of the request. The random boundary - string can be explicitly set with the ``multipart_boundary`` parameter. - - :param method: - HTTP request method (such as GET, POST, PUT, etc.) - - :param url: - The URL to perform the request on. - - :param fields: - Data to encode and send in the request body. - - :param headers: - Dictionary of custom headers to send, such as User-Agent, - If-None-Match, etc. If None, pool headers are used. If provided, - these headers completely replace any pool-specific headers. - - :param encode_multipart: - If True, encode the ``fields`` using the multipart/form-data MIME - format. - - :param multipart_boundary: - If not specified, then a random boundary will be generated using - :func:`urllib3.filepost.choose_boundary`. - """ - if headers is None: - headers = self.headers - - extra_kw: dict[str, typing.Any] = {"headers": HTTPHeaderDict(headers)} - body: bytes | str - - if fields: - if "body" in urlopen_kw: - raise TypeError( - "request got values for both 'fields' and 'body', can only specify one." - ) - - if encode_multipart: - body, content_type = encode_multipart_formdata( - fields, boundary=multipart_boundary - ) - else: - body, content_type = ( - urlencode(fields), # type: ignore[arg-type] - "application/x-www-form-urlencoded", - ) - - extra_kw["body"] = body - extra_kw["headers"].setdefault("Content-Type", content_type) - - extra_kw.update(urlopen_kw) - - return self.urlopen(method, url, **extra_kw) diff --git a/venv/lib/python3.12/site-packages/urllib3/_version.py b/venv/lib/python3.12/site-packages/urllib3/_version.py index bfed039..d49df2a 100644 --- a/venv/lib/python3.12/site-packages/urllib3/_version.py +++ b/venv/lib/python3.12/site-packages/urllib3/_version.py @@ -1,24 +1,2 @@ -# file generated by vcs-versioning -# don't change, don't track in version control -from __future__ import annotations - -__all__ = [ - "__version__", - "__version_tuple__", - "version", - "version_tuple", - "__commit_id__", - "commit_id", -] - -version: str -__version__: str -__version_tuple__: tuple[int | str, ...] -version_tuple: tuple[int | str, ...] -commit_id: str | None -__commit_id__: str | None - -__version__ = version = '2.7.0' -__version_tuple__ = version_tuple = (2, 7, 0) - -__commit_id__ = commit_id = None +# This file is protected via CODEOWNERS +__version__ = "1.26.20" diff --git a/venv/lib/python3.12/site-packages/urllib3/connection.py b/venv/lib/python3.12/site-packages/urllib3/connection.py index 84e1dab..de35b63 100644 --- a/venv/lib/python3.12/site-packages/urllib3/connection.py +++ b/venv/lib/python3.12/site-packages/urllib3/connection.py @@ -1,59 +1,59 @@ -from __future__ import annotations +from __future__ import absolute_import import datetime -import http.client import logging import os import re import socket -import sys -import threading -import typing import warnings -from http.client import HTTPConnection as _HTTPConnection -from http.client import HTTPException as HTTPException # noqa: F401 -from http.client import ResponseNotReady +from socket import error as SocketError from socket import timeout as SocketTimeout -if typing.TYPE_CHECKING: - from .response import HTTPResponse - from .util.ssl_ import _TYPE_PEER_CERT_RET_DICT - from .util.ssltransport import SSLTransport - -from ._collections import HTTPHeaderDict -from .http2 import probe as http2_probe -from .util.response import assert_header_parsing -from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT, Timeout -from .util.util import to_str -from .util.wait import wait_for_read +from .packages import six +from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection +from .packages.six.moves.http_client import HTTPException # noqa: F401 +from .util.proxy import create_proxy_ssl_context try: # Compiled with SSL? import ssl BaseSSLError = ssl.SSLError -except (ImportError, AttributeError): - ssl = None # type: ignore[assignment] +except (ImportError, AttributeError): # Platform-specific: No SSL. + ssl = None - class BaseSSLError(BaseException): # type: ignore[no-redef] + class BaseSSLError(BaseException): pass -from ._base_connection import _TYPE_BODY -from ._base_connection import ProxyConfig as ProxyConfig -from ._base_connection import _ResponseOptions as _ResponseOptions +try: + # Python 3: not a no-op, we're adding this to the namespace so it can be imported. + ConnectionError = ConnectionError +except NameError: + # Python 2 + class ConnectionError(Exception): + pass + + +try: # Python 3: + # Not a no-op, we're adding this to the namespace so it can be imported. + BrokenPipeError = BrokenPipeError +except NameError: # Python 2: + + class BrokenPipeError(Exception): + pass + + +from ._collections import HTTPHeaderDict # noqa (historical, removed in v2) from ._version import __version__ from .exceptions import ( ConnectTimeoutError, - HeaderParsingError, - NameResolutionError, NewConnectionError, - ProxyError, + SubjectAltNameWarning, SystemTimeWarning, ) -from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection, ssl_ -from .util.request import body_to_chunks -from .util.ssl_ import assert_fingerprint as _assert_fingerprint +from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection from .util.ssl_ import ( + assert_fingerprint, create_urllib3_context, is_ipaddress, resolve_cert_reqs, @@ -61,12 +61,6 @@ from .util.ssl_ import ( ssl_wrap_socket, ) from .util.ssl_match_hostname import CertificateError, match_hostname -from .util.url import Url - -# Not a no-op, we're adding this to the namespace so it can be imported. -ConnectionError = ConnectionError -BrokenPipeError = BrokenPipeError - log = logging.getLogger(__name__) @@ -74,12 +68,12 @@ port_by_scheme = {"http": 80, "https": 443} # When it comes time to update this value as a part of regular maintenance # (ie test_recent_date is failing) update it to ~6 months before the current date. -RECENT_DATE = datetime.date(2025, 1, 1) +RECENT_DATE = datetime.date(2024, 1, 1) _CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") -class HTTPConnection(_HTTPConnection): +class HTTPConnection(_HTTPConnection, object): """ Based on :class:`http.client.HTTPConnection` but provides an extra constructor backwards-compatibility layer between older and newer Pythons. @@ -87,6 +81,7 @@ class HTTPConnection(_HTTPConnection): Additional keyword parameters are used to configure attributes of the connection. Accepted parameters include: + - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` - ``source_address``: Set the source address for the current connection. - ``socket_options``: Set specific options on the underlying socket. If not specified, then defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling @@ -104,70 +99,38 @@ class HTTPConnection(_HTTPConnection): Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). """ - default_port: typing.ClassVar[int] = port_by_scheme["http"] # type: ignore[misc] + default_port = port_by_scheme["http"] #: Disable Nagle's algorithm by default. #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` - default_socket_options: typing.ClassVar[connection._TYPE_SOCKET_OPTIONS] = [ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - ] + default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] #: Whether this connection verifies the host's certificate. - is_verified: bool = False + is_verified = False - #: Whether this proxy connection verified the proxy host's certificate. - # If no proxy is currently connected to the value will be ``None``. - proxy_is_verified: bool | None = None + #: Whether this proxy connection (if used) verifies the proxy host's + #: certificate. + proxy_is_verified = None - blocksize: int - source_address: tuple[str, int] | None - socket_options: connection._TYPE_SOCKET_OPTIONS | None + def __init__(self, *args, **kw): + if not six.PY2: + kw.pop("strict", None) - _has_connected_to_proxy: bool - _response_options: _ResponseOptions | None - _tunnel_host: str | None - _tunnel_port: int | None - _tunnel_scheme: str | None + # Pre-set source_address. + self.source_address = kw.get("source_address") - def __init__( - self, - host: str, - port: int | None = None, - *, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - blocksize: int = 16384, - socket_options: None | ( - connection._TYPE_SOCKET_OPTIONS - ) = default_socket_options, - proxy: Url | None = None, - proxy_config: ProxyConfig | None = None, - ) -> None: - super().__init__( - host=host, - port=port, - timeout=Timeout.resolve_default_timeout(timeout), - source_address=source_address, - blocksize=blocksize, - ) - self.socket_options = socket_options - self.proxy = proxy - self.proxy_config = proxy_config + #: The socket options provided by the user. If no options are + #: provided, we use the default options. + self.socket_options = kw.pop("socket_options", self.default_socket_options) - self._has_connected_to_proxy = False - self._response_options = None - self._tunnel_host: str | None = None - self._tunnel_port: int | None = None - self._tunnel_scheme: str | None = None + # Proxy options provided by the user. + self.proxy = kw.pop("proxy", None) + self.proxy_config = kw.pop("proxy_config", None) - def __str__(self) -> str: - return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})" - - def __repr__(self) -> str: - return f"<{self} at {id(self):#x}>" + _HTTPConnection.__init__(self, *args, **kw) @property - def host(self) -> str: + def host(self): """ Getter method to remove any trailing dots that indicate the hostname is an FQDN. @@ -186,7 +149,7 @@ class HTTPConnection(_HTTPConnection): return self._dns_host.rstrip(".") @host.setter - def host(self, value: str) -> None: + def host(self, value): """ Setter for the `host` property. @@ -195,409 +158,129 @@ class HTTPConnection(_HTTPConnection): """ self._dns_host = value - def _new_conn(self) -> socket.socket: + def _new_conn(self): """Establish a socket connection and set nodelay settings on it. :return: New socket connection. """ + extra_kw = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + try: - sock = connection.create_connection( - (self._dns_host, self.port), - self.timeout, - source_address=self.source_address, - socket_options=self.socket_options, + conn = connection.create_connection( + (self._dns_host, self.port), self.timeout, **extra_kw ) - except socket.gaierror as e: - raise NameResolutionError(self.host, self, e) from e - except SocketTimeout as e: + + except SocketTimeout: raise ConnectTimeoutError( self, - f"Connection to {self.host} timed out. (connect timeout={self.timeout})", - ) from e - - except OSError as e: - raise NewConnectionError( - self, f"Failed to establish a new connection: {e}" - ) from e - - sys.audit("http.client.connect", self, self.host, self.port) - - return sock - - def set_tunnel( - self, - host: str, - port: int | None = None, - headers: typing.Mapping[str, str] | None = None, - scheme: str = "http", - ) -> None: - if scheme not in ("http", "https"): - raise ValueError( - f"Invalid proxy scheme for tunneling: {scheme!r}, must be either 'http' or 'https'" + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), ) - super().set_tunnel(host, port=port, headers=headers) - self._tunnel_scheme = scheme - if sys.version_info < (3, 11, 9) or ((3, 12) <= sys.version_info < (3, 12, 3)): - # Taken from python/cpython#100986 which was backported in 3.11.9 and 3.12.3. - # When using connection_from_host, host will come without brackets. - def _wrap_ipv6(self, ip: bytes) -> bytes: - if b":" in ip and ip[0] != b"["[0]: - return b"[" + ip + b"]" - return ip + except SocketError as e: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e + ) - if sys.version_info < (3, 11, 9): - # `_tunnel` copied from 3.11.13 backporting - # https://github.com/python/cpython/commit/0d4026432591d43185568dd31cef6a034c4b9261 - # and https://github.com/python/cpython/commit/6fbc61070fda2ffb8889e77e3b24bca4249ab4d1 - def _tunnel(self) -> None: - _MAXLINE = http.client._MAXLINE # type: ignore[attr-defined] - connect = b"CONNECT %s:%d HTTP/1.0\r\n" % ( # type: ignore[str-format] - self._wrap_ipv6(self._tunnel_host.encode("ascii")), # type: ignore[union-attr] - self._tunnel_port, - ) - headers = [connect] - for header, value in self._tunnel_headers.items(): # type: ignore[attr-defined] - headers.append(f"{header}: {value}\r\n".encode("latin-1")) - headers.append(b"\r\n") - # Making a single send() call instead of one per line encourages - # the host OS to use a more optimal packet size instead of - # potentially emitting a series of small packets. - self.send(b"".join(headers)) - del headers + return conn - response = self.response_class(self.sock, method=self._method) # type: ignore[attr-defined] - try: - (version, code, message) = response._read_status() # type: ignore[attr-defined] - - if code != http.HTTPStatus.OK: - self.close() - raise OSError( - f"Tunnel connection failed: {code} {message.strip()}" - ) - while True: - line = response.fp.readline(_MAXLINE + 1) - if len(line) > _MAXLINE: - raise http.client.LineTooLong("header line") - if not line: - # for sites which EOF without sending a trailer - break - if line in (b"\r\n", b"\n", b""): - break - - if self.debuglevel > 0: - print("header:", line.decode()) - finally: - response.close() - - elif (3, 12) <= sys.version_info < (3, 12, 3): - # `_tunnel` copied from 3.12.11 backporting - # https://github.com/python/cpython/commit/23aef575c7629abcd4aaf028ebd226fb41a4b3c8 - def _tunnel(self) -> None: # noqa: F811 - connect = b"CONNECT %s:%d HTTP/1.1\r\n" % ( # type: ignore[str-format] - self._wrap_ipv6(self._tunnel_host.encode("idna")), # type: ignore[union-attr] - self._tunnel_port, - ) - headers = [connect] - for header, value in self._tunnel_headers.items(): # type: ignore[attr-defined] - headers.append(f"{header}: {value}\r\n".encode("latin-1")) - headers.append(b"\r\n") - # Making a single send() call instead of one per line encourages - # the host OS to use a more optimal packet size instead of - # potentially emitting a series of small packets. - self.send(b"".join(headers)) - del headers - - response = self.response_class(self.sock, method=self._method) # type: ignore[attr-defined] - try: - (version, code, message) = response._read_status() # type: ignore[attr-defined] - - self._raw_proxy_headers = http.client._read_headers(response.fp) # type: ignore[attr-defined] - - if self.debuglevel > 0: - for header in self._raw_proxy_headers: - print("header:", header.decode()) - - if code != http.HTTPStatus.OK: - self.close() - raise OSError( - f"Tunnel connection failed: {code} {message.strip()}" - ) - - finally: - response.close() - - def connect(self) -> None: - self.sock = self._new_conn() - if self._tunnel_host: - # If we're tunneling it means we're connected to our proxy. - self._has_connected_to_proxy = True + def _is_using_tunnel(self): + # Google App Engine's httplib does not define _tunnel_host + return getattr(self, "_tunnel_host", None) + def _prepare_conn(self, conn): + self.sock = conn + if self._is_using_tunnel(): # TODO: Fix tunnel so it doesn't depend on self.sock state. self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 - # If there's a proxy to be connected to we are fully connected. - # This is set twice (once above and here) due to forwarding proxies - # not using tunnelling. - self._has_connected_to_proxy = bool(self.proxy) + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) - if self._has_connected_to_proxy: - self.proxy_is_verified = False - - @property - def is_closed(self) -> bool: - return self.sock is None - - @property - def is_connected(self) -> bool: - if self.sock is None: - return False - return not wait_for_read(self.sock, timeout=0.0) - - @property - def has_connected_to_proxy(self) -> bool: - return self._has_connected_to_proxy - - @property - def proxy_is_forwarding(self) -> bool: - """ - Return True if a forwarding proxy is configured, else return False - """ - return bool(self.proxy) and self._tunnel_host is None - - @property - def proxy_is_tunneling(self) -> bool: - """ - Return True if a tunneling proxy is configured, else return False - """ - return self._tunnel_host is not None - - def close(self) -> None: - try: - super().close() - finally: - # Reset all stateful properties so connection - # can be re-used without leaking prior configs. - self.sock = None - self.is_verified = False - self.proxy_is_verified = None - self._has_connected_to_proxy = False - self._response_options = None - self._tunnel_host = None - self._tunnel_port = None - self._tunnel_scheme = None - - def putrequest( - self, - method: str, - url: str, - skip_host: bool = False, - skip_accept_encoding: bool = False, - ) -> None: - """""" + def putrequest(self, method, url, *args, **kwargs): + """ """ # Empty docstring because the indentation of CPython's implementation # is broken but we don't want this method in our documentation. match = _CONTAINS_CONTROL_CHAR_RE.search(method) if match: raise ValueError( - f"Method cannot contain non-token characters {method!r} (found at least {match.group()!r})" + "Method cannot contain non-token characters %r (found at least %r)" + % (method, match.group()) ) - return super().putrequest( - method, url, skip_host=skip_host, skip_accept_encoding=skip_accept_encoding - ) + return _HTTPConnection.putrequest(self, method, url, *args, **kwargs) - def putheader(self, header: str, *values: str) -> None: # type: ignore[override] - """""" + def putheader(self, header, *values): + """ """ if not any(isinstance(v, str) and v == SKIP_HEADER for v in values): - super().putheader(header, *values) - elif to_str(header.lower()) not in SKIPPABLE_HEADERS: - skippable_headers = "', '".join( - [str.title(header) for header in sorted(SKIPPABLE_HEADERS)] - ) + _HTTPConnection.putheader(self, header, *values) + elif six.ensure_str(header.lower()) not in SKIPPABLE_HEADERS: raise ValueError( - f"urllib3.util.SKIP_HEADER only supports '{skippable_headers}'" + "urllib3.util.SKIP_HEADER only supports '%s'" + % ("', '".join(map(str.title, sorted(SKIPPABLE_HEADERS))),) ) - # `request` method's signature intentionally violates LSP. - # urllib3's API is different from `http.client.HTTPConnection` and the subclassing is only incidental. - def request( # type: ignore[override] - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - *, - chunked: bool = False, - preload_content: bool = True, - decode_content: bool = True, - enforce_content_length: bool = True, - ) -> None: + def request(self, method, url, body=None, headers=None): # Update the inner socket's timeout value to send the request. # This only triggers if the connection is re-used. - if self.sock is not None: + if getattr(self, "sock", None) is not None: self.sock.settimeout(self.timeout) - # Store these values to be fed into the HTTPResponse - # object later. TODO: Remove this in favor of a real - # HTTP lifecycle mechanism. - - # We have to store these before we call .request() - # because sometimes we can still salvage a response - # off the wire even if we aren't able to completely - # send the request body. - self._response_options = _ResponseOptions( - request_method=method, - request_url=url, - preload_content=preload_content, - decode_content=decode_content, - enforce_content_length=enforce_content_length, - ) - if headers is None: headers = {} - header_keys = frozenset(to_str(k.lower()) for k in headers) + else: + # Avoid modifying the headers passed into .request() + headers = headers.copy() + if "user-agent" not in (six.ensure_str(k.lower()) for k in headers): + headers["User-Agent"] = _get_default_user_agent() + super(HTTPConnection, self).request(method, url, body=body, headers=headers) + + def request_chunked(self, method, url, body=None, headers=None): + """ + Alternative to the common request method, which sends the + body with chunked encoding and not as one block + """ + headers = headers or {} + header_keys = set([six.ensure_str(k.lower()) for k in headers]) skip_accept_encoding = "accept-encoding" in header_keys skip_host = "host" in header_keys self.putrequest( method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host ) - - # Transform the body into an iterable of sendall()-able chunks - # and detect if an explicit Content-Length is doable. - chunks_and_cl = body_to_chunks(body, method=method, blocksize=self.blocksize) - chunks = chunks_and_cl.chunks - content_length = chunks_and_cl.content_length - - # When chunked is explicit set to 'True' we respect that. - if chunked: - if "transfer-encoding" not in header_keys: - self.putheader("Transfer-Encoding", "chunked") - else: - # Detect whether a framing mechanism is already in use. If so - # we respect that value, otherwise we pick chunked vs content-length - # depending on the type of 'body'. - if "content-length" in header_keys: - chunked = False - elif "transfer-encoding" in header_keys: - chunked = True - - # Otherwise we go off the recommendation of 'body_to_chunks()'. - else: - chunked = False - if content_length is None: - if chunks is not None: - chunked = True - self.putheader("Transfer-Encoding", "chunked") - else: - self.putheader("Content-Length", str(content_length)) - - # Now that framing headers are out of the way we send all the other headers. if "user-agent" not in header_keys: self.putheader("User-Agent", _get_default_user_agent()) for header, value in headers.items(): self.putheader(header, value) + if "transfer-encoding" not in header_keys: + self.putheader("Transfer-Encoding", "chunked") self.endheaders() - # If we're given a body we start sending that in chunks. - if chunks is not None: - for chunk in chunks: - # Sending empty chunks isn't allowed for TE: chunked - # as it indicates the end of the body. + if body is not None: + stringish_types = six.string_types + (bytes,) + if isinstance(body, stringish_types): + body = (body,) + for chunk in body: if not chunk: continue - if isinstance(chunk, str): - chunk = chunk.encode("utf-8") - if chunked: - self.send(b"%x\r\n%b\r\n" % (len(chunk), chunk)) - else: - self.send(chunk) + if not isinstance(chunk, bytes): + chunk = chunk.encode("utf8") + len_str = hex(len(chunk))[2:] + to_send = bytearray(len_str.encode()) + to_send += b"\r\n" + to_send += chunk + to_send += b"\r\n" + self.send(to_send) - # Regardless of whether we have a body or not, if we're in - # chunked mode we want to send an explicit empty chunk. - if chunked: - self.send(b"0\r\n\r\n") - - def request_chunked( - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - ) -> None: - """ - Alternative to the common request method, which sends the - body with chunked encoding and not as one block - """ - warnings.warn( - "HTTPConnection.request_chunked() is deprecated and will be removed " - "in urllib3 v3.0. Instead use HTTPConnection.request(..., chunked=True).", - category=FutureWarning, - stacklevel=2, - ) - self.request(method, url, body=body, headers=headers, chunked=True) - - def getresponse( # type: ignore[override] - self, - ) -> HTTPResponse: - """ - Get the response from the server. - - If the HTTPConnection is in the correct state, returns an instance of HTTPResponse or of whatever object is returned by the response_class variable. - - If a request has not been sent or if a previous response has not be handled, ResponseNotReady is raised. If the HTTP response indicates that the connection should be closed, then it will be closed before the response is returned. When the connection is closed, the underlying socket is closed. - """ - # Raise the same error as http.client.HTTPConnection - if self._response_options is None: - raise ResponseNotReady() - - # Reset this attribute for being used again. - resp_options = self._response_options - self._response_options = None - - # Since the connection's timeout value may have been updated - # we need to set the timeout on the socket. - self.sock.settimeout(self.timeout) - - # This is needed here to avoid circular import errors - from .response import HTTPResponse - - # Save a reference to the shutdown function before ownership is passed - # to httplib_response - # TODO should we implement it everywhere? - _shutdown = getattr(self.sock, "shutdown", None) - - # Get the response from http.client.HTTPConnection - httplib_response = super().getresponse() - - try: - assert_header_parsing(httplib_response.msg) - except (HeaderParsingError, TypeError) as hpe: - log.warning( - "Failed to parse headers (url=%s): %s", - _url_from_connection(self, resp_options.request_url), - hpe, - exc_info=True, - ) - - headers = HTTPHeaderDict(httplib_response.msg.items()) - - response = HTTPResponse( - body=httplib_response, - headers=headers, - status=httplib_response.status, - version=httplib_response.version, - version_string=getattr(self, "_http_vsn_str", "HTTP/?"), - reason=httplib_response.reason, - preload_content=resp_options.preload_content, - decode_content=resp_options.decode_content, - original_response=httplib_response, - enforce_content_length=resp_options.enforce_content_length, - request_method=resp_options.request_method, - request_url=resp_options.request_url, - sock_shutdown=_shutdown, - ) - return response + # After the if clause, to always have a closed body + self.send(b"0\r\n\r\n") class HTTPSConnection(HTTPConnection): @@ -606,103 +289,57 @@ class HTTPSConnection(HTTPConnection): socket by means of :py:func:`urllib3.util.ssl_wrap_socket`. """ - default_port = port_by_scheme["https"] # type: ignore[misc] + default_port = port_by_scheme["https"] - cert_reqs: int | str | None = None - ca_certs: str | None = None - ca_cert_dir: str | None = None - ca_cert_data: None | str | bytes = None - ssl_version: int | str | None = None - ssl_minimum_version: int | None = None - ssl_maximum_version: int | None = None - assert_fingerprint: str | None = None - _connect_callback: typing.Callable[..., None] | None = None + cert_reqs = None + ca_certs = None + ca_cert_dir = None + ca_cert_data = None + ssl_version = None + assert_fingerprint = None + tls_in_tls_required = False def __init__( self, - host: str, - port: int | None = None, - *, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - blocksize: int = 16384, - socket_options: None | ( - connection._TYPE_SOCKET_OPTIONS - ) = HTTPConnection.default_socket_options, - proxy: Url | None = None, - proxy_config: ProxyConfig | None = None, - cert_reqs: int | str | None = None, - assert_hostname: None | str | typing.Literal[False] = None, - assert_fingerprint: str | None = None, - server_hostname: str | None = None, - ssl_context: ssl.SSLContext | None = None, - ca_certs: str | None = None, - ca_cert_dir: str | None = None, - ca_cert_data: None | str | bytes = None, - ssl_minimum_version: int | None = None, - ssl_maximum_version: int | None = None, - ssl_version: int | str | None = None, # Deprecated - cert_file: str | None = None, - key_file: str | None = None, - key_password: str | None = None, - ) -> None: - super().__init__( - host, - port=port, - timeout=timeout, - source_address=source_address, - blocksize=blocksize, - socket_options=socket_options, - proxy=proxy, - proxy_config=proxy_config, - ) + host, + port=None, + key_file=None, + cert_file=None, + key_password=None, + strict=None, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + ssl_context=None, + server_hostname=None, + **kw + ): + + HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw) self.key_file = key_file self.cert_file = cert_file self.key_password = key_password self.ssl_context = ssl_context self.server_hostname = server_hostname - self.assert_hostname = assert_hostname - self.assert_fingerprint = assert_fingerprint - self.ssl_version = ssl_version - self.ssl_minimum_version = ssl_minimum_version - self.ssl_maximum_version = ssl_maximum_version - self.ca_certs = ca_certs and os.path.expanduser(ca_certs) - self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) - self.ca_cert_data = ca_cert_data - # cert_reqs depends on ssl_context so calculate last. - if cert_reqs is None: - if self.ssl_context is not None: - cert_reqs = self.ssl_context.verify_mode - else: - cert_reqs = resolve_cert_reqs(None) - self.cert_reqs = cert_reqs - self._connect_callback = None + # Required property for Google AppEngine 1.9.0 which otherwise causes + # HTTPS requests to go out as HTTP. (See Issue #356) + self._protocol = "https" def set_cert( self, - key_file: str | None = None, - cert_file: str | None = None, - cert_reqs: int | str | None = None, - key_password: str | None = None, - ca_certs: str | None = None, - assert_hostname: None | str | typing.Literal[False] = None, - assert_fingerprint: str | None = None, - ca_cert_dir: str | None = None, - ca_cert_data: None | str | bytes = None, - ) -> None: + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + ca_cert_data=None, + ): """ This method should only be called once, before the connection is used. """ - warnings.warn( - "HTTPSConnection.set_cert() is deprecated and will be removed " - "in urllib3 v3.0. Instead provide the parameters to the " - "HTTPSConnection constructor.", - category=FutureWarning, - stacklevel=2, - ) - # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also # have an SSLContext object in which case we'll use its verify_mode. if cert_reqs is None: @@ -721,322 +358,191 @@ class HTTPSConnection(HTTPConnection): self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) self.ca_cert_data = ca_cert_data - def connect(self) -> None: - # Today we don't need to be doing this step before the /actual/ socket - # connection, however in the future we'll need to decide whether to - # create a new socket or re-use an existing "shared" socket as a part - # of the HTTP/2 handshake dance. - if self._tunnel_host is not None and self._tunnel_port is not None: - probe_http2_host = self._tunnel_host - probe_http2_port = self._tunnel_port - else: - probe_http2_host = self.host - probe_http2_port = self.port + def connect(self): + # Add certificate verification + self.sock = conn = self._new_conn() + hostname = self.host + tls_in_tls = False - # Check if the target origin supports HTTP/2. - # If the value comes back as 'None' it means that the current thread - # is probing for HTTP/2 support. Otherwise, we're waiting for another - # probe to complete, or we get a value right away. - target_supports_http2: bool | None - if "h2" in ssl_.ALPN_PROTOCOLS: - target_supports_http2 = http2_probe.acquire_and_get( - host=probe_http2_host, port=probe_http2_port - ) - else: - # If HTTP/2 isn't going to be offered it doesn't matter if - # the target supports HTTP/2. Don't want to make a probe. - target_supports_http2 = False + if self._is_using_tunnel(): + if self.tls_in_tls_required: + self.sock = conn = self._connect_tls_proxy(hostname, conn) + tls_in_tls = True - if self._connect_callback is not None: - self._connect_callback( - "before connect", - thread_id=threading.get_ident(), - target_supports_http2=target_supports_http2, + # Calls self._set_hostport(), so self.host is + # self._tunnel_host below. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + # Override the host with the one we're requesting data from. + hostname = self._tunnel_host + + server_hostname = hostname + if self.server_hostname is not None: + server_hostname = self.server_hostname + + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn( + ( + "System time is way off (before {0}). This will probably " + "lead to SSL verification errors" + ).format(RECENT_DATE), + SystemTimeWarning, ) - try: - sock: socket.socket | ssl.SSLSocket - self.sock = sock = self._new_conn() - server_hostname: str = self.host - tls_in_tls = False + # Wrap socket using verification with the root certs in + # trusted_root_certs + default_ssl_context = False + if self.ssl_context is None: + default_ssl_context = True + self.ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(self.ssl_version), + cert_reqs=resolve_cert_reqs(self.cert_reqs), + ) - # Do we need to establish a tunnel? - if self.proxy_is_tunneling: - # We're tunneling to an HTTPS origin so need to do TLS-in-TLS. - if self._tunnel_scheme == "https": - # _connect_tls_proxy will verify and assign proxy_is_verified - self.sock = sock = self._connect_tls_proxy(self.host, sock) - tls_in_tls = True - elif self._tunnel_scheme == "http": - self.proxy_is_verified = False + context = self.ssl_context + context.verify_mode = resolve_cert_reqs(self.cert_reqs) - # If we're tunneling it means we're connected to our proxy. - self._has_connected_to_proxy = True + # Try to load OS default certs if none are given. + # Works well on Windows (requires Python3.4+) + if ( + not self.ca_certs + and not self.ca_cert_dir + and not self.ca_cert_data + and default_ssl_context + and hasattr(context, "load_default_certs") + ): + context.load_default_certs() - self._tunnel() - # Override the host with the one we're requesting data from. - server_hostname = typing.cast(str, self._tunnel_host) + self.sock = ssl_wrap_socket( + sock=conn, + keyfile=self.key_file, + certfile=self.cert_file, + key_password=self.key_password, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=server_hostname, + ssl_context=context, + tls_in_tls=tls_in_tls, + ) - if self.server_hostname is not None: - server_hostname = self.server_hostname + # If we're using all defaults and the connection + # is TLSv1 or TLSv1.1 we throw a DeprecationWarning + # for the host. + if ( + default_ssl_context + and self.ssl_version is None + and hasattr(self.sock, "version") + and self.sock.version() in {"TLSv1", "TLSv1.1"} + ): # Defensive: + warnings.warn( + "Negotiating TLSv1/TLSv1.1 by default is deprecated " + "and will be disabled in urllib3 v2.0.0. Connecting to " + "'%s' with '%s' can be enabled by explicitly opting-in " + "with 'ssl_version'" % (self.host, self.sock.version()), + DeprecationWarning, + ) - is_time_off = datetime.date.today() < RECENT_DATE - if is_time_off: + if self.assert_fingerprint: + assert_fingerprint( + self.sock.getpeercert(binary_form=True), self.assert_fingerprint + ) + elif ( + context.verify_mode != ssl.CERT_NONE + and not getattr(context, "check_hostname", False) + and self.assert_hostname is not False + ): + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = self.sock.getpeercert() + if not cert.get("subjectAltName", ()): warnings.warn( ( - f"System time is way off (before {RECENT_DATE}). This will probably " - "lead to SSL verification errors" + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) ), - SystemTimeWarning, + SubjectAltNameWarning, ) + _match_hostname(cert, self.assert_hostname or server_hostname) - # Remove trailing '.' from fqdn hostnames to allow certificate validation - server_hostname_rm_dot = server_hostname.rstrip(".") + self.is_verified = ( + context.verify_mode == ssl.CERT_REQUIRED + or self.assert_fingerprint is not None + ) - sock_and_verified = _ssl_wrap_socket_and_match_hostname( - sock=sock, - cert_reqs=self.cert_reqs, - ssl_version=self.ssl_version, - ssl_minimum_version=self.ssl_minimum_version, - ssl_maximum_version=self.ssl_maximum_version, - ca_certs=self.ca_certs, - ca_cert_dir=self.ca_cert_dir, - ca_cert_data=self.ca_cert_data, - cert_file=self.cert_file, - key_file=self.key_file, - key_password=self.key_password, - server_hostname=server_hostname_rm_dot, - ssl_context=self.ssl_context, - tls_in_tls=tls_in_tls, - assert_hostname=self.assert_hostname, - assert_fingerprint=self.assert_fingerprint, - ) - self.sock = sock_and_verified.socket - - # If an error occurs during connection/handshake we may need to release - # our lock so another connection can probe the origin. - except BaseException: - if self._connect_callback is not None: - self._connect_callback( - "after connect failure", - thread_id=threading.get_ident(), - target_supports_http2=target_supports_http2, - ) - - if target_supports_http2 is None: - http2_probe.set_and_release( - host=probe_http2_host, port=probe_http2_port, supports_http2=None - ) - raise - - # If this connection doesn't know if the origin supports HTTP/2 - # we report back to the HTTP/2 probe our result. - if target_supports_http2 is None: - supports_http2 = sock_and_verified.socket.selected_alpn_protocol() == "h2" - http2_probe.set_and_release( - host=probe_http2_host, - port=probe_http2_port, - supports_http2=supports_http2, - ) - - # Forwarding proxies can never have a verified target since - # the proxy is the one doing the verification. Should instead - # use a CONNECT tunnel in order to verify the target. - # See: https://github.com/urllib3/urllib3/issues/3267. - if self.proxy_is_forwarding: - self.is_verified = False - else: - self.is_verified = sock_and_verified.is_verified - - # If there's a proxy to be connected to we are fully connected. - # This is set twice (once above and here) due to forwarding proxies - # not using tunnelling. - self._has_connected_to_proxy = bool(self.proxy) - - # Set `self.proxy_is_verified` unless it's already set while - # establishing a tunnel. - if self._has_connected_to_proxy and self.proxy_is_verified is None: - self.proxy_is_verified = sock_and_verified.is_verified - - def _connect_tls_proxy(self, hostname: str, sock: socket.socket) -> ssl.SSLSocket: + def _connect_tls_proxy(self, hostname, conn): """ Establish a TLS connection to the proxy using the provided SSL context. """ - # `_connect_tls_proxy` is called when self._tunnel_host is truthy. - proxy_config = typing.cast(ProxyConfig, self.proxy_config) + proxy_config = self.proxy_config ssl_context = proxy_config.ssl_context - sock_and_verified = _ssl_wrap_socket_and_match_hostname( - sock, - cert_reqs=self.cert_reqs, - ssl_version=self.ssl_version, - ssl_minimum_version=self.ssl_minimum_version, - ssl_maximum_version=self.ssl_maximum_version, + if ssl_context: + # If the user provided a proxy context, we assume CA and client + # certificates have already been set + return ssl_wrap_socket( + sock=conn, + server_hostname=hostname, + ssl_context=ssl_context, + ) + + ssl_context = create_proxy_ssl_context( + self.ssl_version, + self.cert_reqs, + self.ca_certs, + self.ca_cert_dir, + self.ca_cert_data, + ) + + # If no cert was provided, use only the default options for server + # certificate validation + socket = ssl_wrap_socket( + sock=conn, ca_certs=self.ca_certs, ca_cert_dir=self.ca_cert_dir, ca_cert_data=self.ca_cert_data, server_hostname=hostname, ssl_context=ssl_context, - assert_hostname=proxy_config.assert_hostname, - assert_fingerprint=proxy_config.assert_fingerprint, - # Features that aren't implemented for proxies yet: - cert_file=None, - key_file=None, - key_password=None, - tls_in_tls=False, ) - self.proxy_is_verified = sock_and_verified.is_verified - return sock_and_verified.socket # type: ignore[return-value] - -class _WrappedAndVerifiedSocket(typing.NamedTuple): - """ - Wrapped socket and whether the connection is - verified after the TLS handshake - """ - - socket: ssl.SSLSocket | SSLTransport - is_verified: bool - - -def _ssl_wrap_socket_and_match_hostname( - sock: socket.socket, - *, - cert_reqs: None | str | int, - ssl_version: None | str | int, - ssl_minimum_version: int | None, - ssl_maximum_version: int | None, - cert_file: str | None, - key_file: str | None, - key_password: str | None, - ca_certs: str | None, - ca_cert_dir: str | None, - ca_cert_data: None | str | bytes, - assert_hostname: None | str | typing.Literal[False], - assert_fingerprint: str | None, - server_hostname: str | None, - ssl_context: ssl.SSLContext | None, - tls_in_tls: bool = False, -) -> _WrappedAndVerifiedSocket: - """Logic for constructing an SSLContext from all TLS parameters, passing - that down into ssl_wrap_socket, and then doing certificate verification - either via hostname or fingerprint. This function exists to guarantee - that both proxies and targets have the same behavior when connecting via TLS. - """ - default_ssl_context = False - if ssl_context is None: - default_ssl_context = True - context = create_urllib3_context( - ssl_version=resolve_ssl_version(ssl_version), - ssl_minimum_version=ssl_minimum_version, - ssl_maximum_version=ssl_maximum_version, - cert_reqs=resolve_cert_reqs(cert_reqs), - ) - else: - context = ssl_context - - context.verify_mode = resolve_cert_reqs(cert_reqs) - - # In some cases, we want to verify hostnames ourselves - if ( - # `ssl` can't verify fingerprints or alternate hostnames - assert_fingerprint - or assert_hostname - # assert_hostname can be set to False to disable hostname checking - or assert_hostname is False - # We still support OpenSSL 1.0.2, which prevents us from verifying - # hostnames easily: https://github.com/pyca/pyopenssl/pull/933 - or ssl_.IS_PYOPENSSL - or not ssl_.HAS_NEVER_CHECK_COMMON_NAME - ): - context.check_hostname = False - - # Try to load OS default certs if none are given. We need to do the hasattr() check - # for custom pyOpenSSL SSLContext objects because they don't support - # load_default_certs(). - if ( - not ca_certs - and not ca_cert_dir - and not ca_cert_data - and default_ssl_context - and hasattr(context, "load_default_certs") - ): - context.load_default_certs() - - # Ensure that IPv6 addresses are in the proper format and don't have a - # scope ID. Python's SSL module fails to recognize scoped IPv6 addresses - # and interprets them as DNS hostnames. - if server_hostname is not None: - normalized = server_hostname.strip("[]") - if "%" in normalized: - normalized = normalized[: normalized.rfind("%")] - if is_ipaddress(normalized): - server_hostname = normalized - - ssl_sock = ssl_wrap_socket( - sock=sock, - keyfile=key_file, - certfile=cert_file, - key_password=key_password, - ca_certs=ca_certs, - ca_cert_dir=ca_cert_dir, - ca_cert_data=ca_cert_data, - server_hostname=server_hostname, - ssl_context=context, - tls_in_tls=tls_in_tls, - ) - - try: - if assert_fingerprint: - _assert_fingerprint( - ssl_sock.getpeercert(binary_form=True), assert_fingerprint - ) - elif ( - context.verify_mode != ssl.CERT_NONE - and not context.check_hostname - and assert_hostname is not False + if ssl_context.verify_mode != ssl.CERT_NONE and not getattr( + ssl_context, "check_hostname", False ): - cert: _TYPE_PEER_CERT_RET_DICT = ssl_sock.getpeercert() # type: ignore[assignment] - - # Need to signal to our match_hostname whether to use 'commonName' or not. - # If we're using our own constructed SSLContext we explicitly set 'False' - # because PyPy hard-codes 'True' from SSLContext.hostname_checks_common_name. - if default_ssl_context: - hostname_checks_common_name = False - else: - hostname_checks_common_name = ( - getattr(context, "hostname_checks_common_name", False) or False + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = socket.getpeercert() + if not cert.get("subjectAltName", ()): + warnings.warn( + ( + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) + ), + SubjectAltNameWarning, ) + _match_hostname(cert, hostname) - _match_hostname( - cert, - assert_hostname or server_hostname, # type: ignore[arg-type] - hostname_checks_common_name, - ) - - return _WrappedAndVerifiedSocket( - socket=ssl_sock, - is_verified=context.verify_mode == ssl.CERT_REQUIRED - or bool(assert_fingerprint), - ) - except BaseException: - ssl_sock.close() - raise + self.proxy_is_verified = ssl_context.verify_mode == ssl.CERT_REQUIRED + return socket -def _match_hostname( - cert: _TYPE_PEER_CERT_RET_DICT | None, - asserted_hostname: str, - hostname_checks_common_name: bool = False, -) -> None: +def _match_hostname(cert, asserted_hostname): # Our upstream implementation of ssl.match_hostname() # only applies this normalization to IP addresses so it doesn't # match DNS SANs so we do the same thing! - stripped_hostname = asserted_hostname.strip("[]") + stripped_hostname = asserted_hostname.strip("u[]") if is_ipaddress(stripped_hostname): asserted_hostname = stripped_hostname try: - match_hostname(cert, asserted_hostname, hostname_checks_common_name) + match_hostname(cert, asserted_hostname) except CertificateError as e: log.warning( "Certificate did not match expected hostname: %s. Certificate: %s", @@ -1045,55 +551,22 @@ def _match_hostname( ) # Add cert to exception and reraise so client code can inspect # the cert when catching the exception, if they want to - e._peer_cert = cert # type: ignore[attr-defined] + e._peer_cert = cert raise -def _wrap_proxy_error(err: Exception, proxy_scheme: str | None) -> ProxyError: - # Look for the phrase 'wrong version number', if found - # then we should warn the user that we're very sure that - # this proxy is HTTP-only and they have a configuration issue. - error_normalized = " ".join(re.split("[^a-z]", str(err).lower())) - is_likely_http_proxy = ( - "wrong version number" in error_normalized - or "unknown protocol" in error_normalized - or "record layer failure" in error_normalized - ) - http_proxy_warning = ( - ". Your proxy appears to only use HTTP and not HTTPS, " - "try changing your proxy URL to be HTTP. See: " - "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" - "#https-proxy-error-http-proxy" - ) - new_err = ProxyError( - f"Unable to connect to proxy" - f"{http_proxy_warning if is_likely_http_proxy and proxy_scheme == 'https' else ''}", - err, - ) - new_err.__cause__ = err - return new_err +def _get_default_user_agent(): + return "python-urllib3/%s" % __version__ -def _get_default_user_agent() -> str: - return f"python-urllib3/{__version__}" - - -class DummyConnection: +class DummyConnection(object): """Used to detect a failed ConnectionCls import.""" + pass + if not ssl: - HTTPSConnection = DummyConnection # type: ignore[misc, assignment] # noqa: F811 + HTTPSConnection = DummyConnection # noqa: F811 VerifiedHTTPSConnection = HTTPSConnection - - -def _url_from_connection( - conn: HTTPConnection | HTTPSConnection, path: str | None = None -) -> str: - """Returns the URL from a given connection. This is mainly used for testing and logging.""" - - scheme = "https" if isinstance(conn, HTTPSConnection) else "http" - - return Url(scheme=scheme, host=conn.host, port=conn.port, path=path).url diff --git a/venv/lib/python3.12/site-packages/urllib3/connectionpool.py b/venv/lib/python3.12/site-packages/urllib3/connectionpool.py index 70fbc5e..0872ed7 100644 --- a/venv/lib/python3.12/site-packages/urllib3/connectionpool.py +++ b/venv/lib/python3.12/site-packages/urllib3/connectionpool.py @@ -1,18 +1,15 @@ -from __future__ import annotations +from __future__ import absolute_import import errno import logging -import queue +import re +import socket import sys -import typing import warnings -import weakref +from socket import error as SocketError from socket import timeout as SocketTimeout -from types import TracebackType -from ._base_connection import _TYPE_BODY from ._collections import HTTPHeaderDict -from ._request_methods import RequestMethods from .connection import ( BaseSSLError, BrokenPipeError, @@ -20,14 +17,13 @@ from .connection import ( HTTPConnection, HTTPException, HTTPSConnection, - ProxyConfig, - _wrap_proxy_error, + VerifiedHTTPSConnection, + port_by_scheme, ) -from .connection import port_by_scheme as port_by_scheme from .exceptions import ( ClosedPoolError, EmptyPoolError, - FullPoolError, + HeaderParsingError, HostChangedError, InsecureRequestWarning, LocationValueError, @@ -39,32 +35,38 @@ from .exceptions import ( SSLError, TimeoutError, ) -from .response import BaseHTTPResponse +from .packages import six +from .packages.six.moves import queue +from .request import RequestMethods +from .response import HTTPResponse from .util.connection import is_connection_dropped from .util.proxy import connection_requires_http_tunnel -from .util.request import _TYPE_BODY_POSITION, set_file_position +from .util.queue import LifoQueue +from .util.request import set_file_position +from .util.response import assert_header_parsing from .util.retry import Retry from .util.ssl_match_hostname import CertificateError -from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_DEFAULT, Timeout +from .util.timeout import Timeout from .util.url import Url, _encode_target from .util.url import _normalize_host as normalize_host -from .util.url import parse_url -from .util.util import to_str +from .util.url import get_host, parse_url -if typing.TYPE_CHECKING: - import ssl +try: # Platform-specific: Python 3 + import weakref - from typing_extensions import Self + weakref_finalize = weakref.finalize +except AttributeError: # Platform-specific: Python 2 + from .packages.backports.weakref_finalize import weakref_finalize - from ._base_connection import BaseHTTPConnection, BaseHTTPSConnection +xrange = six.moves.xrange log = logging.getLogger(__name__) -_TYPE_TIMEOUT = typing.Union[Timeout, float, _TYPE_DEFAULT, None] +_Default = object() # Pool objects -class ConnectionPool: +class ConnectionPool(object): """ Base class for all connection pools, such as :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. @@ -75,42 +77,33 @@ class ConnectionPool: target URIs. """ - scheme: str | None = None - QueueCls = queue.LifoQueue + scheme = None + QueueCls = LifoQueue - def __init__(self, host: str, port: int | None = None) -> None: + def __init__(self, host, port=None): if not host: raise LocationValueError("No host specified.") self.host = _normalize_host(host, scheme=self.scheme) + self._proxy_host = host.lower() self.port = port - # This property uses 'normalize_host()' (not '_normalize_host()') - # to avoid removing square braces around IPv6 addresses. - # This value is sent to `HTTPConnection.set_tunnel()` if called - # because square braces are required for HTTP CONNECT tunneling. - self._tunnel_host = normalize_host(host, scheme=self.scheme).lower() + def __str__(self): + return "%s(host=%r, port=%r)" % (type(self).__name__, self.host, self.port) - def __str__(self) -> str: - return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})" - - def __enter__(self) -> Self: + def __enter__(self): return self - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> typing.Literal[False]: + def __exit__(self, exc_type, exc_val, exc_tb): self.close() # Return False to re-raise any potential exceptions return False - def close(self) -> None: + def close(self): """ Close all pooled connections and disable the pool. """ + pass # This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 @@ -129,6 +122,14 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): Port used for this HTTP Connection (None is equivalent to 80), passed into :class:`http.client.HTTPConnection`. + :param strict: + Causes BadStatusLine to be raised if the status line can't be parsed + as a valid HTTP/1.0 or 1.1 status line, passed into + :class:`http.client.HTTPConnection`. + + .. note:: + Only works in Python 2. This parameter is ignored in Python 3. + :param timeout: Socket timeout in seconds for each individual connection. This can be a float or integer, which sets the timeout for the HTTP request, @@ -170,25 +171,29 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): """ scheme = "http" - ConnectionCls: type[BaseHTTPConnection] | type[BaseHTTPSConnection] = HTTPConnection + ConnectionCls = HTTPConnection + ResponseCls = HTTPResponse def __init__( self, - host: str, - port: int | None = None, - timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT, - maxsize: int = 1, - block: bool = False, - headers: typing.Mapping[str, str] | None = None, - retries: Retry | bool | int | None = None, - _proxy: Url | None = None, - _proxy_headers: typing.Mapping[str, str] | None = None, - _proxy_config: ProxyConfig | None = None, - **conn_kw: typing.Any, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + _proxy_config=None, + **conn_kw ): ConnectionPool.__init__(self, host, port) RequestMethods.__init__(self, headers) + self.strict = strict + if not isinstance(timeout, Timeout): timeout = Timeout.from_float(timeout) @@ -198,7 +203,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): self.timeout = timeout self.retries = retries - self.pool: queue.LifoQueue[typing.Any] | None = self.QueueCls(maxsize) + self.pool = self.QueueCls(maxsize) self.block = block self.proxy = _proxy @@ -206,7 +211,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): self.proxy_config = _proxy_config # Fill the queue up so that doing get() on it will block properly - for _ in range(maxsize): + for _ in xrange(maxsize): self.pool.put(None) # These are mostly for testing and debugging purposes. @@ -216,8 +221,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): if self.proxy: # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. - # Defaulting `socket_options` to an empty list avoids it defaulting to - # ``HTTPConnection.default_socket_options``. + # We cannot know if the user has added default socket options, so we cannot replace the + # list. self.conn_kw.setdefault("socket_options", []) self.conn_kw["proxy"] = self.proxy @@ -231,9 +236,9 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # Close all the HTTPConnections in the pool before the # HTTPConnectionPool object is garbage collected. - weakref.finalize(self, _close_pool_connections, pool) + weakref_finalize(self, _close_pool_connections, pool) - def _new_conn(self) -> BaseHTTPConnection: + def _new_conn(self): """ Return a fresh :class:`HTTPConnection`. """ @@ -249,11 +254,12 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): host=self.host, port=self.port, timeout=self.timeout.connect_timeout, - **self.conn_kw, + strict=self.strict, + **self.conn_kw ) return conn - def _get_conn(self, timeout: float | None = None) -> BaseHTTPConnection: + def _get_conn(self, timeout=None): """ Get a connection. Will return a pooled connection if one is available. @@ -266,32 +272,33 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :prop:`.block` is ``True``. """ conn = None - - if self.pool is None: - raise ClosedPoolError(self, "Pool is closed.") - try: conn = self.pool.get(block=self.block, timeout=timeout) except AttributeError: # self.pool is None - raise ClosedPoolError(self, "Pool is closed.") from None # Defensive: + raise ClosedPoolError(self, "Pool is closed.") except queue.Empty: if self.block: raise EmptyPoolError( self, - "Pool is empty and a new connection can't be opened due to blocking mode.", - ) from None + "Pool reached maximum size and no more connections are allowed.", + ) pass # Oh well, we'll create a new connection then # If this is a persistent connection, check if it got disconnected if conn and is_connection_dropped(conn): log.debug("Resetting dropped connection: %s", self.host) conn.close() + if getattr(conn, "auto_open", 1) == 0: + # This is a proxied connection that has been mutated by + # http.client._tunnel() and cannot be reused (since it would + # attempt to bypass the proxy) + conn = None return conn or self._new_conn() - def _put_conn(self, conn: BaseHTTPConnection | None) -> None: + def _put_conn(self, conn): """ Put a connection back into the pool. @@ -305,47 +312,36 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): If the pool is closed, then the connection will be closed and discarded. """ - if self.pool is not None: - try: - self.pool.put(conn, block=False) - return # Everything is dandy, done. - except AttributeError: - # self.pool is None. - pass - except queue.Full: - # Connection never got put back into the pool, close it. - if conn: - conn.close() - - if self.block: - # This should never happen if you got the conn from self._get_conn - raise FullPoolError( - self, - "Pool reached maximum size and no more connections are allowed.", - ) from None - - log.warning( - "Connection pool is full, discarding connection: %s. Connection pool size: %s", - self.host, - self.pool.qsize(), - ) - + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except queue.Full: + # This should never happen if self.block == True + log.warning( + "Connection pool is full, discarding connection: %s. Connection pool size: %s", + self.host, + self.pool.qsize(), + ) # Connection never got put back into the pool, close it. if conn: conn.close() - def _validate_conn(self, conn: BaseHTTPConnection) -> None: + def _validate_conn(self, conn): """ Called right before a request is made, after the socket is created. """ + pass - def _prepare_proxy(self, conn: BaseHTTPConnection) -> None: + def _prepare_proxy(self, conn): # Nothing to do for HTTP connections. pass - def _get_timeout(self, timeout: _TYPE_TIMEOUT) -> Timeout: + def _get_timeout(self, timeout): """Helper that always returns a :class:`urllib3.util.Timeout`""" - if timeout is _DEFAULT_TIMEOUT: + if timeout is _Default: return self.timeout.clone() if isinstance(timeout, Timeout): @@ -355,40 +351,34 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # can be removed later return Timeout.from_float(timeout) - def _raise_timeout( - self, - err: BaseSSLError | OSError | SocketTimeout, - url: str, - timeout_value: _TYPE_TIMEOUT | None, - ) -> None: + def _raise_timeout(self, err, url, timeout_value): """Is the error actually a timeout? Will raise a ReadTimeout or pass""" if isinstance(err, SocketTimeout): raise ReadTimeoutError( - self, url, f"Read timed out. (read timeout={timeout_value})" - ) from err + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) - # See the above comment about EAGAIN in Python 3. + # See the above comment about EAGAIN in Python 3. In Python 2 we have + # to specifically catch it and throw the timeout error if hasattr(err, "errno") and err.errno in _blocking_errnos: raise ReadTimeoutError( - self, url, f"Read timed out. (read timeout={timeout_value})" - ) from err + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + # Catch possible read timeouts thrown as SSL errors. If not the + # case, rethrow the original. We need to do this because of: + # http://bugs.python.org/issue10272 + if "timed out" in str(err) or "did not complete (read)" in str( + err + ): # Python < 2.7.4 + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) def _make_request( - self, - conn: BaseHTTPConnection, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - retries: Retry | None = None, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - chunked: bool = False, - response_conn: BaseHTTPConnection | None = None, - preload_content: bool = True, - decode_content: bool = True, - enforce_content_length: bool = True, - ) -> BaseHTTPResponse: + self, conn, method, url, timeout=_Default, chunked=False, **httplib_request_kw + ): """ Perform a request on a given urllib connection object taken from our pool. @@ -396,6 +386,185 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :param conn: a connection from one of our connection pools + :param timeout: + Socket timeout in seconds for the request. This can be a + float or integer, which will set the same timeout value for + the socket connect and the socket read, or an instance of + :class:`urllib3.util.Timeout`, which gives you more fine-grained + control over your timeouts. + """ + self.num_requests += 1 + + timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout) + + # Trigger any extra validation we need to do. + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise + + # conn.request() calls http.client.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + try: + if chunked: + conn.request_chunked(method, url, **httplib_request_kw) + else: + conn.request(method, url, **httplib_request_kw) + + # We are swallowing BrokenPipeError (errno.EPIPE) since the server is + # legitimately able to close the connection after sending a valid response. + # With this behaviour, the received response is still readable. + except BrokenPipeError: + # Python 3 + pass + except IOError as e: + # Python 2 and macOS/Linux + # EPIPE and ESHUTDOWN are BrokenPipeError on Python 2, and EPROTOTYPE/ECONNRESET are needed on macOS + # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ + if e.errno not in { + errno.EPIPE, + errno.ESHUTDOWN, + errno.EPROTOTYPE, + errno.ECONNRESET, + }: + raise + + # Reset the timeout for the recv() on the socket + read_timeout = timeout_obj.read_timeout + + # App Engine doesn't have a sock attr + if getattr(conn, "sock", None): + # In Python 3 socket.py will catch EAGAIN and return None when you + # try and read into the file pointer created by http.client, which + # instead raises a BadStatusLine exception. Instead of catching + # the exception and assuming all BadStatusLine exceptions are read + # timeouts, check for a zero timeout before making the request. + if read_timeout == 0: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % read_timeout + ) + if read_timeout is Timeout.DEFAULT_TIMEOUT: + conn.sock.settimeout(socket.getdefaulttimeout()) + else: # None or a value + conn.sock.settimeout(read_timeout) + + # Receive the response from the server + try: + try: + # Python 2.7, use buffering of HTTP responses + httplib_response = conn.getresponse(buffering=True) + except TypeError: + # Python 3 + try: + httplib_response = conn.getresponse() + except BaseException as e: + # Remove the TypeError from the exception chain in + # Python 3 (including for exceptions like SystemExit). + # Otherwise it looks like a bug in the code. + six.raise_from(e, None) + except (SocketTimeout, BaseSSLError, SocketError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) + raise + + # AppEngine doesn't have a version attr. + http_version = getattr(conn, "_http_vsn_str", "HTTP/?") + log.debug( + '%s://%s:%s "%s %s %s" %s %s', + self.scheme, + self.host, + self.port, + method, + url, + http_version, + httplib_response.status, + httplib_response.length, + ) + + try: + assert_header_parsing(httplib_response.msg) + except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 + log.warning( + "Failed to parse headers (url=%s): %s", + self._absolute_url(url), + hpe, + exc_info=True, + ) + + return httplib_response + + def _absolute_url(self, path): + return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + if self.pool is None: + return + # Disable access to the pool + old_pool, self.pool = self.pool, None + + # Close all the HTTPConnections in the pool. + _close_pool_connections(old_pool) + + def is_same_host(self, url): + """ + Check if the given ``url`` is a member of the same host as this + connection pool. + """ + if url.startswith("/"): + return True + + # TODO: Add optional support for socket.gethostbyname checking. + scheme, host, port = get_host(url) + if host is not None: + host = _normalize_host(host, scheme=scheme) + + # Use explicit default port for comparison when none is given + if self.port and not port: + port = port_by_scheme.get(scheme) + elif not self.port and port == port_by_scheme.get(scheme): + port = None + + return (scheme, host, port) == (self.scheme, self.host, self.port) + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + assert_same_host=True, + timeout=_Default, + pool_timeout=None, + release_conn=None, + chunked=False, + body_pos=None, + **response_kw + ): + """ + Get a connection from the pool and perform an HTTP request. This is the + lowest level call for making a request, so you'll need to specify all + the raw details. + + .. note:: + + More commonly, it's appropriate to use a convenience method provided + by :class:`.RequestMethods`, such as :meth:`request`. + + .. note:: + + `release_conn` will only behave as expected if + `preload_content=False` because we want to make + `preload_content=False` the default behaviour someday soon without + breaking backwards compatibility. + :param method: HTTP request method (such as GET, POST, PUT, etc.) @@ -427,234 +596,6 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. - :param timeout: - If specified, overrides the default timeout for this one - request. It may be a float (in seconds) or an instance of - :class:`urllib3.util.Timeout`. - - :param chunked: - If True, urllib3 will send the body using chunked transfer - encoding. Otherwise, urllib3 will send the body using the standard - content-length form. Defaults to False. - - :param response_conn: - Set this to ``None`` if you will handle releasing the connection or - set the connection to have the response release it. - - :param preload_content: - If True, the response's body will be preloaded during construction. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - - :param enforce_content_length: - Enforce content length checking. Body returned by server must match - value of Content-Length header, if present. Otherwise, raise error. - """ - self.num_requests += 1 - - timeout_obj = self._get_timeout(timeout) - timeout_obj.start_connect() - conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout) - - try: - # Trigger any extra validation we need to do. - try: - self._validate_conn(conn) - except (SocketTimeout, BaseSSLError) as e: - self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) - raise - - # _validate_conn() starts the connection to an HTTPS proxy - # so we need to wrap errors with 'ProxyError' here too. - except ( - OSError, - NewConnectionError, - TimeoutError, - BaseSSLError, - CertificateError, - SSLError, - ) as e: - new_e: Exception = e - if isinstance(e, (BaseSSLError, CertificateError)): - new_e = SSLError(e) - # If the connection didn't successfully connect to it's proxy - # then there - if isinstance( - new_e, (OSError, NewConnectionError, TimeoutError, SSLError) - ) and (conn and conn.proxy and not conn.has_connected_to_proxy): - new_e = _wrap_proxy_error(new_e, conn.proxy.scheme) - raise new_e - - # conn.request() calls http.client.*.request, not the method in - # urllib3.request. It also calls makefile (recv) on the socket. - try: - conn.request( - method, - url, - body=body, - headers=headers, - chunked=chunked, - preload_content=preload_content, - decode_content=decode_content, - enforce_content_length=enforce_content_length, - ) - - # We are swallowing BrokenPipeError (errno.EPIPE) since the server is - # legitimately able to close the connection after sending a valid response. - # With this behaviour, the received response is still readable. - except BrokenPipeError: - pass - except OSError as e: - # MacOS/Linux - # EPROTOTYPE and ECONNRESET are needed on macOS - # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ - # Condition changed later to emit ECONNRESET instead of only EPROTOTYPE. - if e.errno != errno.EPROTOTYPE and e.errno != errno.ECONNRESET: - raise - - # Reset the timeout for the recv() on the socket - read_timeout = timeout_obj.read_timeout - - if not conn.is_closed: - # In Python 3 socket.py will catch EAGAIN and return None when you - # try and read into the file pointer created by http.client, which - # instead raises a BadStatusLine exception. Instead of catching - # the exception and assuming all BadStatusLine exceptions are read - # timeouts, check for a zero timeout before making the request. - if read_timeout == 0: - raise ReadTimeoutError( - self, url, f"Read timed out. (read timeout={read_timeout})" - ) - conn.timeout = read_timeout - - # Receive the response from the server - try: - response = conn.getresponse() - except (BaseSSLError, OSError) as e: - self._raise_timeout(err=e, url=url, timeout_value=read_timeout) - raise - - # Set properties that are used by the pooling layer. - response.retries = retries - response._connection = response_conn # type: ignore[attr-defined] - response._pool = self # type: ignore[attr-defined] - - log.debug( - '%s://%s:%s "%s %s %s" %s %s', - self.scheme, - self.host, - self.port, - method, - url, - response.version_string, - response.status, - response.length_remaining, - ) - - return response - - def close(self) -> None: - """ - Close all pooled connections and disable the pool. - """ - if self.pool is None: - return - # Disable access to the pool - old_pool, self.pool = self.pool, None - - # Close all the HTTPConnections in the pool. - _close_pool_connections(old_pool) - - def is_same_host(self, url: str) -> bool: - """ - Check if the given ``url`` is a member of the same host as this - connection pool. - """ - if url.startswith("/"): - return True - - # TODO: Add optional support for socket.gethostbyname checking. - scheme, _, host, port, *_ = parse_url(url) - scheme = scheme or "http" - if host is not None: - host = _normalize_host(host, scheme=scheme) - - # Use explicit default port for comparison when none is given - if self.port and not port: - port = port_by_scheme.get(scheme) - elif not self.port and port == port_by_scheme.get(scheme): - port = None - - return (scheme, host, port) == (self.scheme, self.host, self.port) - - def urlopen( # type: ignore[override] - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - retries: Retry | bool | int | None = None, - redirect: bool = True, - assert_same_host: bool = True, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - pool_timeout: int | None = None, - release_conn: bool | None = None, - chunked: bool = False, - body_pos: _TYPE_BODY_POSITION | None = None, - preload_content: bool = True, - decode_content: bool = True, - **response_kw: typing.Any, - ) -> BaseHTTPResponse: - """ - Get a connection from the pool and perform an HTTP request. This is the - lowest level call for making a request, so you'll need to specify all - the raw details. - - .. note:: - - More commonly, it's appropriate to use a convenience method - such as :meth:`request`. - - .. note:: - - `release_conn` will only behave as expected if - `preload_content=False` because we want to make - `preload_content=False` the default behaviour someday soon without - breaking backwards compatibility. - - :param method: - HTTP request method (such as GET, POST, PUT, etc.) - - :param url: - The URL to perform the request on. - - :param body: - Data to send in the request body, either :class:`str`, :class:`bytes`, - an iterable of :class:`str`/:class:`bytes`, or a file-like object. - - :param headers: - Dictionary of custom headers to send, such as User-Agent, - If-None-Match, etc. If None, pool headers are used. If provided, - these headers completely replace any pool-specific headers. - - :param retries: - Configure the number of retries to allow before raising a - :class:`~urllib3.exceptions.MaxRetryError` exception. - - If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a - :class:`~urllib3.util.retry.Retry` object for fine-grained control - over different types of retries. - Pass an integer number to retry connection errors that many times, - but no other types of errors. Pass zero to never retry. - - If ``False``, then retries are disabled and any exception is raised - immediately. Also, instead of raising a MaxRetryError on redirects, - the redirect response will be returned. - - :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. - :param redirect: If True, automatically handle redirects (status codes 301, 302, 303, 307, 308). Each redirect counts as a retry. Disabling retries @@ -675,13 +616,6 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): block for ``pool_timeout`` seconds and raise EmptyPoolError if no connection is available within the time period. - :param bool preload_content: - If True, the response's body will be preloaded into memory. - - :param bool decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - :param release_conn: If False, then the urlopen call will not release the connection back into the pool once a response is received (but will release if @@ -689,10 +623,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): `preload_content=True`). This is useful if you're not preloading the response's content immediately. You will need to call ``r.release_conn()`` on the response ``r`` to return the connection - back into the pool. If None, it takes the value of ``preload_content`` - which defaults to ``True``. + back into the pool. If None, it takes the value of + ``response_kw.get('preload_content', True)``. - :param bool chunked: + :param chunked: If True, urllib3 will send the body using chunked transfer encoding. Otherwise, urllib3 will send the body using the standard content-length form. Defaults to False. @@ -701,16 +635,14 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): Position to seek to in file-like body in the event of a retry or redirect. Typically this won't need to be set because urllib3 will auto-populate the value when needed. + + :param \\**response_kw: + Additional parameters are passed to + :meth:`urllib3.response.HTTPResponse.from_httplib` """ - # Ensure that the URL we're connecting to is properly encoded - if url.startswith("/"): - # URLs starting with / are inherently schemeless. - url = to_str(_encode_target(url)) - destination_scheme = None - else: - parsed_url = parse_url(url) - destination_scheme = parsed_url.scheme - url = to_str(parsed_url.url) + + parsed_url = parse_url(url) + destination_scheme = parsed_url.scheme if headers is None: headers = self.headers @@ -719,12 +651,18 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): retries = Retry.from_int(retries, redirect=redirect, default=self.retries) if release_conn is None: - release_conn = preload_content + release_conn = response_kw.get("preload_content", True) # Check host if assert_same_host and not self.is_same_host(url): raise HostChangedError(self, url, retries) + # Ensure that the URL we're connecting to is properly encoded + if url.startswith("/"): + url = six.ensure_str(_encode_target(url)) + else: + url = six.ensure_str(parsed_url.url) + conn = None # Track whether `conn` needs to be released before @@ -746,8 +684,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # have to copy the headers dict so we can safely change it without those # changes being reflected in anyone else's copy. if not http_tunnel_required: - headers = headers.copy() # type: ignore[attr-defined] - headers.update(self.proxy_headers) # type: ignore[union-attr] + headers = headers.copy() + headers.update(self.proxy_headers) # Must keep the exception bound to a separate variable or else Python 3 # complains about UnboundLocalError. @@ -766,26 +704,16 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): timeout_obj = self._get_timeout(timeout) conn = self._get_conn(timeout=pool_timeout) - conn.timeout = timeout_obj.connect_timeout # type: ignore[assignment] + conn.timeout = timeout_obj.connect_timeout - # Is this a closed/new connection that requires CONNECT tunnelling? - if self.proxy is not None and http_tunnel_required and conn.is_closed: - try: - self._prepare_proxy(conn) - except (BaseSSLError, OSError, SocketTimeout) as e: - self._raise_timeout( - err=e, url=self.proxy.url, timeout_value=conn.timeout - ) - raise + is_new_proxy_conn = self.proxy is not None and not getattr( + conn, "sock", None + ) + if is_new_proxy_conn and http_tunnel_required: + self._prepare_proxy(conn) - # If we're going to release the connection in ``finally:``, then - # the response doesn't need to know about the connection. Otherwise - # it will also try to release it and we'll have a double-release - # mess. - response_conn = conn if not release_conn else None - - # Make the request on the HTTPConnection object - response = self._make_request( + # Make the request on the httplib connection object. + httplib_response = self._make_request( conn, method, url, @@ -793,11 +721,24 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): body=body, headers=headers, chunked=chunked, + ) + + # If we're going to release the connection in ``finally:``, then + # the response doesn't need to know about the connection. Otherwise + # it will also try to release it and we'll have a double-release + # mess. + response_conn = conn if not release_conn else None + + # Pass method to Response for length checking + response_kw["request_method"] = method + + # Import httplib's response into our own wrapper object + response = self.ResponseCls.from_httplib( + httplib_response, + pool=self, + connection=response_conn, retries=retries, - response_conn=response_conn, - preload_content=preload_content, - decode_content=decode_content, - **response_kw, + **response_kw ) # Everything went great! @@ -812,35 +753,54 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): except ( TimeoutError, HTTPException, - OSError, + SocketError, ProtocolError, BaseSSLError, SSLError, CertificateError, - ProxyError, ) as e: # Discard the connection for these exceptions. It will be # replaced during the next _get_conn() call. clean_exit = False - new_e: Exception = e - if isinstance(e, (BaseSSLError, CertificateError)): - new_e = SSLError(e) - if isinstance( - new_e, - ( - OSError, - NewConnectionError, - TimeoutError, - SSLError, - HTTPException, - ), - ) and (conn and conn.proxy and not conn.has_connected_to_proxy): - new_e = _wrap_proxy_error(new_e, conn.proxy.scheme) - elif isinstance(new_e, (OSError, HTTPException)): - new_e = ProtocolError("Connection aborted.", new_e) + + def _is_ssl_error_message_from_http_proxy(ssl_error): + # We're trying to detect the message 'WRONG_VERSION_NUMBER' but + # SSLErrors are kinda all over the place when it comes to the message, + # so we try to cover our bases here! + message = " ".join(re.split("[^a-z]", str(ssl_error).lower())) + return ( + "wrong version number" in message + or "unknown protocol" in message + or "record layer failure" in message + ) + + # Try to detect a common user error with proxies which is to + # set an HTTP proxy to be HTTPS when it should be 'http://' + # (ie {'http': 'http://proxy', 'https': 'https://proxy'}) + # Instead we add a nice error message and point to a URL. + if ( + isinstance(e, BaseSSLError) + and self.proxy + and _is_ssl_error_message_from_http_proxy(e) + and conn.proxy + and conn.proxy.scheme == "https" + ): + e = ProxyError( + "Your proxy appears to only use HTTP and not HTTPS, " + "try changing your proxy URL to be HTTP. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#https-proxy-error-http-proxy", + SSLError(e), + ) + elif isinstance(e, (BaseSSLError, CertificateError)): + e = SSLError(e) + elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: + e = ProxyError("Cannot connect to proxy.", e) + elif isinstance(e, (SocketError, HTTPException)): + e = ProtocolError("Connection aborted.", e) retries = retries.increment( - method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2] + method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2] ) retries.sleep() @@ -853,9 +813,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # to throw the connection away unless explicitly told not to. # Close the connection, set the variable to None, and make sure # we put the None back in the pool to avoid leaking it. - if conn: - conn.close() - conn = None + conn = conn and conn.close() release_this_conn = True if release_this_conn: @@ -882,9 +840,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): release_conn=release_conn, chunked=chunked, body_pos=body_pos, - preload_content=preload_content, - decode_content=decode_content, - **response_kw, + **response_kw ) # Handle redirect? @@ -897,18 +853,6 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): body = None headers = HTTPHeaderDict(headers)._prepare_for_method_change() - # Strip headers marked as unsafe to forward to the redirected location. - # Check remove_headers_on_redirect to avoid a potential network call within - # self.is_same_host() which may use socket.gethostbyname() in the future. - if retries.remove_headers_on_redirect and not self.is_same_host( - redirect_location - ): - new_headers = headers.copy() # type: ignore[union-attr] - for header in headers: - if header.lower() in retries.remove_headers_on_redirect: - new_headers.pop(header, None) - headers = new_headers - try: retries = retries.increment(method, url, response=response, _pool=self) except MaxRetryError: @@ -933,9 +877,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): release_conn=release_conn, chunked=chunked, body_pos=body_pos, - preload_content=preload_content, - decode_content=decode_content, - **response_kw, + **response_kw ) # Check if we should retry the HTTP response. @@ -965,9 +907,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): release_conn=release_conn, chunked=chunked, body_pos=body_pos, - preload_content=preload_content, - decode_content=decode_content, - **response_kw, + **response_kw ) return response @@ -988,35 +928,37 @@ class HTTPSConnectionPool(HTTPConnectionPool): """ scheme = "https" - ConnectionCls: type[BaseHTTPSConnection] = HTTPSConnection + ConnectionCls = HTTPSConnection def __init__( self, - host: str, - port: int | None = None, - timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT, - maxsize: int = 1, - block: bool = False, - headers: typing.Mapping[str, str] | None = None, - retries: Retry | bool | int | None = None, - _proxy: Url | None = None, - _proxy_headers: typing.Mapping[str, str] | None = None, - key_file: str | None = None, - cert_file: str | None = None, - cert_reqs: int | str | None = None, - key_password: str | None = None, - ca_certs: str | None = None, - ssl_version: int | str | None = None, - ssl_minimum_version: ssl.TLSVersion | None = None, - ssl_maximum_version: ssl.TLSVersion | None = None, - assert_hostname: str | typing.Literal[False] | None = None, - assert_fingerprint: str | None = None, - ca_cert_dir: str | None = None, - **conn_kw: typing.Any, - ) -> None: - super().__init__( + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + ssl_version=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + **conn_kw + ): + + HTTPConnectionPool.__init__( + self, host, port, + strict, timeout, maxsize, block, @@ -1024,7 +966,7 @@ class HTTPSConnectionPool(HTTPConnectionPool): retries, _proxy, _proxy_headers, - **conn_kw, + **conn_kw ) self.key_file = key_file @@ -1034,29 +976,47 @@ class HTTPSConnectionPool(HTTPConnectionPool): self.ca_certs = ca_certs self.ca_cert_dir = ca_cert_dir self.ssl_version = ssl_version - self.ssl_minimum_version = ssl_minimum_version - self.ssl_maximum_version = ssl_maximum_version self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint - def _prepare_proxy(self, conn: HTTPSConnection) -> None: # type: ignore[override] - """Establishes a tunnel connection through HTTP CONNECT.""" - if self.proxy and self.proxy.scheme == "https": - tunnel_scheme = "https" - else: - tunnel_scheme = "http" + def _prepare_conn(self, conn): + """ + Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` + and establish the tunnel if proxy is used. + """ + + if isinstance(conn, VerifiedHTTPSConnection): + conn.set_cert( + key_file=self.key_file, + key_password=self.key_password, + cert_file=self.cert_file, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ) + conn.ssl_version = self.ssl_version + return conn + + def _prepare_proxy(self, conn): + """ + Establishes a tunnel connection through HTTP CONNECT. + + Tunnel connection is established early because otherwise httplib would + improperly set Host: header to proxy's IP:port. + """ + + conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) + + if self.proxy.scheme == "https": + conn.tls_in_tls_required = True - conn.set_tunnel( - scheme=tunnel_scheme, - host=self._tunnel_host, - port=self.port, - headers=self.proxy_headers, - ) conn.connect() - def _new_conn(self) -> BaseHTTPSConnection: + def _new_conn(self): """ - Return a fresh :class:`urllib3.connection.HTTPConnection`. + Return a fresh :class:`http.client.HTTPSConnection`. """ self.num_connections += 1 log.debug( @@ -1066,59 +1026,64 @@ class HTTPSConnectionPool(HTTPConnectionPool): self.port or "443", ) - if not self.ConnectionCls or self.ConnectionCls is DummyConnection: # type: ignore[comparison-overlap] - raise ImportError( + if not self.ConnectionCls or self.ConnectionCls is DummyConnection: + raise SSLError( "Can't connect to HTTPS URL because the SSL module is not available." ) - actual_host: str = self.host + actual_host = self.host actual_port = self.port - if self.proxy is not None and self.proxy.host is not None: + if self.proxy is not None: actual_host = self.proxy.host actual_port = self.proxy.port - return self.ConnectionCls( + conn = self.ConnectionCls( host=actual_host, port=actual_port, timeout=self.timeout.connect_timeout, + strict=self.strict, cert_file=self.cert_file, key_file=self.key_file, key_password=self.key_password, - cert_reqs=self.cert_reqs, - ca_certs=self.ca_certs, - ca_cert_dir=self.ca_cert_dir, - assert_hostname=self.assert_hostname, - assert_fingerprint=self.assert_fingerprint, - ssl_version=self.ssl_version, - ssl_minimum_version=self.ssl_minimum_version, - ssl_maximum_version=self.ssl_maximum_version, - **self.conn_kw, + **self.conn_kw ) - def _validate_conn(self, conn: BaseHTTPConnection) -> None: + return self._prepare_conn(conn) + + def _validate_conn(self, conn): """ Called right before a request is made, after the socket is created. """ - super()._validate_conn(conn) + super(HTTPSConnectionPool, self)._validate_conn(conn) # Force connect early to allow us to validate the connection. - if conn.is_closed: + if not getattr(conn, "sock", None): # AppEngine might not have `.sock` conn.connect() - # TODO revise this, see https://github.com/urllib3/urllib3/issues/2791 - if not conn.is_verified and not conn.proxy_is_verified: + if not conn.is_verified: warnings.warn( ( - f"Unverified HTTPS request is being made to host '{conn.host}'. " + "Unverified HTTPS request is being made to host '%s'. " "Adding certificate verification is strongly advised. See: " - "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" - "#tls-warnings" + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings" % conn.host + ), + InsecureRequestWarning, + ) + + if getattr(conn, "proxy_is_verified", None) is False: + warnings.warn( + ( + "Unverified HTTPS connection done to an HTTPS proxy. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings" ), InsecureRequestWarning, ) -def connection_from_url(url: str, **kw: typing.Any) -> HTTPConnectionPool: +def connection_from_url(url, **kw): """ Given a url, return an :class:`.ConnectionPool` instance of its host. @@ -1138,24 +1103,15 @@ def connection_from_url(url: str, **kw: typing.Any) -> HTTPConnectionPool: >>> conn = connection_from_url('http://google.com/') >>> r = conn.request('GET', '/') """ - scheme, _, host, port, *_ = parse_url(url) - scheme = scheme or "http" + scheme, host, port = get_host(url) port = port or port_by_scheme.get(scheme, 80) if scheme == "https": - return HTTPSConnectionPool(host, port=port, **kw) # type: ignore[arg-type] + return HTTPSConnectionPool(host, port=port, **kw) else: - return HTTPConnectionPool(host, port=port, **kw) # type: ignore[arg-type] + return HTTPConnectionPool(host, port=port, **kw) -@typing.overload -def _normalize_host(host: None, scheme: str | None) -> None: ... - - -@typing.overload -def _normalize_host(host: str, scheme: str | None) -> str: ... - - -def _normalize_host(host: str | None, scheme: str | None) -> str | None: +def _normalize_host(host, scheme): """ Normalize hosts for comparisons and use with sockets. """ @@ -1168,19 +1124,12 @@ def _normalize_host(host: str | None, scheme: str | None) -> str | None: # Instead, we need to make sure we never pass ``None`` as the port. # However, for backward compatibility reasons we can't actually # *assert* that. See http://bugs.python.org/issue28539 - if host and host.startswith("[") and host.endswith("]"): + if host.startswith("[") and host.endswith("]"): host = host[1:-1] return host -def _url_from_pool( - pool: HTTPConnectionPool | HTTPSConnectionPool, path: str | None = None -) -> str: - """Returns the URL from a given connection pool. This is mainly used for testing and logging.""" - return Url(scheme=pool.scheme, host=pool.host, port=pool.port, path=path).url - - -def _close_pool_connections(pool: queue.LifoQueue[typing.Any]) -> None: +def _close_pool_connections(pool): """Drains a queue of connections and closes each one.""" try: while True: diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/__init__.cpython-312.pyc index 75d2bd84c90bcd43a59c4d36c4c8de64b7748b1f..b56cd45fd08f9a5c83136d5619a00591e3c2ba56 100644 GIT binary patch delta 19 ZcmX@bc#4txG%qg~0}yQHjho259{@6f1quKF delta 19 ZcmX@bc#4txG%qg~0}yPT;6IUjKL9l01%Lnm diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc index d5a65b1ab67d254d1955c45911d01c7352d68701..cf9809d71ba71ead8d026442a552ff823325d484 100644 GIT binary patch delta 11327 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 O+lr19n$rb5!v6~&Py|5$ diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/socks.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/socks.cpython-312.pyc index df39a468dcf197ff97655e132c6c377657309552..7bd944c17b3d9f763c267192bf55e83c30fda3f7 100644 GIT binary patch delta 2494 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 YTfTQax+b>uuH~++xA!6L+6x%)UsF}?`v3p{ diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__init__.py b/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__init__.py deleted file mode 100644 index e5b62b2..0000000 --- a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import annotations - -import urllib3.connection - -from ...connectionpool import HTTPConnectionPool, HTTPSConnectionPool -from .connection import EmscriptenHTTPConnection, EmscriptenHTTPSConnection - - -def inject_into_urllib3() -> None: - # override connection classes to use emscripten specific classes - # n.b. mypy complains about the overriding of classes below - # if it isn't ignored - HTTPConnectionPool.ConnectionCls = EmscriptenHTTPConnection - HTTPSConnectionPool.ConnectionCls = EmscriptenHTTPSConnection - urllib3.connection.HTTPConnection = EmscriptenHTTPConnection # type: ignore[misc,assignment] - urllib3.connection.HTTPSConnection = EmscriptenHTTPSConnection # type: ignore[misc,assignment] - urllib3.connection.VerifiedHTTPSConnection = EmscriptenHTTPSConnection # type: ignore[assignment] diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 13067379120536c859f36a102b036a4018d6c687..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 999 zcma)5&2G~`5Z+B3$4N;M32G=uL{V~RR;5>jQre22N}&=FNWNIc-Vz6U9j(`qa^uhk z;MQm00eA{dNCBi(5QiR6Zb<2=CuWn_`8go=F#6`3ou8eV{Zc9w5uDE-Uv`hO2wf^A zIhh&2lO})*7 zplsDsZm(LF{2f(5a@6A>H=cY4aDha^BQbwALRN}|o|uJ^pe1WUAu(;HQoA=&G6hyX zflKlJ?U82tZ;wKfV*tHw)r(w(;5iVov}om_A8~b)tB1*a%zfW`yDy7QIsv)4*4LYB z(~#BIlzV3$b=M4^&;){_Ye70;Kne4hA3NkIbmD+A5s}F0z;wy;uuY=C=HiXrr=(9= zpFjibT@|Y(qtI-?CipRRJL%@j8 zn)~qEP>*_UbO7Q5x-w9$iHF+OP`f{>9SpVVP}>>Rn&(e{Xj@+uy)&vmS!fSNdoL30 zo7UiEhypvSb1Guaq}B=8h7eo(wF&)oz6uboH z*~q5jQHAqcN~oN?0^-_u6ohW<)5rWSsOmpNRSw1Zc+ zNdmQYj8oYHKT^i2q-+Mc0#_hK zF3oX>O$AfhM4#rg8J^|>U$mqxw2TLAOE>>SOYND{&atFAU(hl6M2pZ z!Sh52T{E$I=|Rd111};DUNh_5q5DYeHW6`9hzg|Hepu)b*9oyFpdpkE-BmUs#G$Mc z%c`cTDV6vR#iuyUl`WOZN><62OY#)tJu9u|p*(xw*@+UO!d|6U5$Ez7RH!BuJ zXzZTO7sYZ#*(ph7343`qD~l+?=nZ&b*1>^&+xP68&g^?>diTDm11Xc{&+OQ_YumxS z)0yerFYerTa9Z=QntB$Rnh#2a?_g(<6c2^_#ZwhgW_@AZwyrg|ES7|f#A;|!nv)et z%og*d69yk726h(ZoRpte#F8!!M$+A4KCdYA#@G)@+4*@<(t@pGqX-%Wjj6V+R>C#nLv|R~ELfM0?sHBp}yC%en#eAaW34 zgXk1uLNENszvO#X2XG(!Cu|h@&fC&1R!bPWx3(}Y^q;q+-NHk{z70nEtYCQI(IgS^gwVYoN zSE5Q~z91ffY8#R`DiDE^G_PE)NI5Z+6$D9?<(0%*!)dGBKA06J97UtdGLK4OCD^7L zj3PHJ%H*iR*roBu%jJUR%FCJ4qLiP>i^9}`3wcKU0IT0tVIc~uCLS?Pl5ygWYF-#L z)nJ(%uK7Z|^@fZ}hExZfcvd%0lAt}T5>O(oLo$RUg=9UFjX-j`k|tKj@bDk<-0dX* zmr0GQkqjV~Y9`2`LZV60uRlU+W(C{QWGHgUR5O=20o$WYngnd$Y$cc>*K8*c$l01T zF{i5wPz9KC7-cmJ#&?u+m7-Wu_&`n3a@6+0)|qX|Z&AypTUW zK3`R4%cV_Y6O-d|UJ*CWXLBdBCq#L?A{C%~(>QEQMS_ZA^BO>we%p=BS2cSklP~3! zOlG04wZHDx0Aog#F&MF&2eL>SF%pSi;9qyB;TS${^>6s-&_9Sb`k#2~%s&qR$E`-Y z;Nem`5;Pa@hKILdCDBd*PPHp}fxlsotMPv1t3B|zVNW!l2`K)RJ$4&9Be~`V!W<;J zU2%_HCROq=H}x^RXXz2hx7J>>&BCQGO0u*C_%ceMOD#PLNOQ`dc9aUNLt$ayzSC}1 z+Kn=BtHQ*+#Nm-12b^o33&>!)Ygp2gz>%H;@-aupCS&y^>))oogN5XMJJ)OMhemYt zi$t}%X}{B89N2QRmC{L3(0ucf2X`U zB=F}PP@}03S_0k$8yQNP>Y%-(=6ILXOd!K@&ZIQe04ORL^;}aCv|32+wfUf`tTo#w zwrl#>&pB((IbW-tfof)BrW~p9(rAsJ3m9ds=QSPfAV)h0`D0kebFOo4kQi#!F;LA_ za~n#NBd^DKrqY@U&OvHc*{^^cmTFF81np`NW)(JSw4RFr1d5qMq&|K^=3)tyq&}5pmD1x37m_pr!1wm;mJm4TJpcCaYd1aO=bb8vE7KlPQ zZJ^olARjLaRm}oAvM96T&-4RR>=CSst102@l5kgPwj$UJpi8A7vT&2Y%|VsSi^$lE zb>O-es?yV#-+^Q&5*(lO9FjpGn$0lMxsw`qTH{373v5I0vnSOG(lqcNpb>OA2cecc z2!{`JQ4fisT6L=~@0(9ueCo=;4Ofrq3%yyrSY7fBs`1V%o8N!*?MJVyzx4CB_~nlH zC4M;)zkKAaBbQ#h8oE0D;o%PsU)}R5|Iz7BbDthu3O;^e`>o)B>JO=*xH>rU;oiUA zduhkzy??Ry)2y0E-Emk#&I`7Nn>hS8?Xm0j*p=`Nd(YRdpc?Ca-}AQTUEdFc^Q7M5 zFL9S9RDZ`!fB$uV|J8}B*tYq?5 zu=gtWq2mKbJvjO)x8$Dq9rWjzT#@k=d~O=sK1n{GbnJM<{Q2Y29Rc$f0XN_;x}!Tb znZMX%0eqJ-$J>JH!*>9HNkr!0xjMbFMQQ5AwQH=+T}vabr2}i^6VC9a4-f^d1)2;^ z^D37zO@Rj+0FzggO4&lw24s8Fsox;z4qMnZM~y;v8Lc7VG^J;Ov|mlG6C${g%$Qsl zyzlkYR_hg%unh=YQ7#|u!zI_yt*3Lx;j!!CF*VV9 z$Hs@f4Z?f8cibd2P!A3+xd-pPR#6GiH<1Le`evf0pMDELe1O1uOs7m#F|hfro|}al za|=E9ofR$r1#~6*fxvv)jF&gO_YMy`r^7>SdcTcw0HO{H|9WT^RG26wfh1dRCMkmE z0&ZST9+$EuAz2d7B+F8AHVX_l zy}z*cK1&MAkcD8*X%W|q*}9^TRLU^v-zL<%m#OYT*>9&GMcFrR!=Ff(D#3~+Iln7xF;l0sYoT6 zy|rY1CRr-uJ~>M_3CzC&?=pE192;C0K?DT?aU(H#hHcX3J`gd?1w#F@>1=iNRd0=P*V?NcU3+6LjNf>*@_diweGc3yJ4kQkD0s(u{T&a7R7*2h7rYJ77ZP(h&|2#0VUUlr>0;| z)Fz>|&@f@MI{;`Qz=9qa?riT@n#F26kf2w`EEkHX`9cx3VJK?rjrELnwlNByXnPOR}6cD>JnNyW)ft5&T;!0>;YQ9XSErOfLNa#XK4#3&~ z_bvs8>-^Jd*S0#pZP}Bk^ND4r=e6A`CBEQmQ)+m$&X20$@j5@g?26R+h#DNM^MlK= z?!`Tq!gan|9ZD_nkxM_T^Qq;HFDyr#__2b`1pkUb>XjrgPw@FDV(_~u3cQIPmM8zDqt z(vfPQv__OTy|+6MJwdv<)lFN$8*jL577y(Qp4d;kVMAmm!HI?gk&^_%v=;OT?LEM| z9ps?EO9s|~kcMm2G6}uJd(`Bh+B=}GTmO9mSj+xU!-7Rt0@JV|vJ+pj;Xvd>@p{8x zv=KpGlyvqqIuON3yt@%c)Jgh=8Yr#Ng`IZ;y8TnmZW+2AHd%T=pw;_>$zticZ3fgp z1j9m}$r9FifSUCpl=nT$0^&EU3fgFoen;?#Cha8JmBi~7DA!2cBL`K}&s>iKOX(yHM()z<5fkJHFvW@R>^7(DsLuV5H^tiN3mJ2K#b5_x427<*h*y+*L((D0;h1Tv1t8~Y=7NyJ8phH4a&G&zn=WCPKjA7OMFj|ap&_sPI41%y6 z2JG?e&;%Y9Kb5jEt0@Vr`+i`M=Am>Ni~@^X0pEn(;TVWzFQ0A$+yQLQyoRW`vDi*& zZXC^2mf@36R+-hD;@Np{V8C{jR_`;qH_?Ph#a0D^|Da7R(V_go8(DK?*f*L?MvlQC z3=eJ9bCH=MXwa;7j@r~$Gur-7P*46hAU`gWJC6{D_o5AAE~?wF1|n)W0TCHB+KV*U zp~iF^tcc+B=5NuI96UGv+~}UOun| z^GVfs#*$yfw*Afi4-b8C=;qMY>qA>Vv)6~V(H;`RUnO4nWb*H~e!TVO`X{fifAX`F z_4O~XZuD-A(2t#9Bqx#J+oiq)TcHAG zX$Q>>uu7A#Rii!yDs>4pF4IiE00cj{m%HP}_>yVO8GRv8>w+_Mzu1CK!+9$n%)u0X@lb{N@O=eJT%BYMRNo`&vN zJZ4F@9E%>X!aa$b?uqN}3Fcsc1of$B)zn4=K*4bD9|p%m9Ua4eBO7#Ueqi1A4}&P` zBA`g-iZJ2miXdXH2qNZ+APN%CNF#(Oimpfpq8Pd&aYUUYJlIGe>LQ&_G`bP>knVMj zUPMXKIoRk!)K8-88Uu(1NvOLqglL#VlZ|zVMo?Y~(J1K|X{<-I0ntW8VE%tFnD z(#CRyJbWaiUo&X<7k-p<9Dc!ML<7+VwPDjV5_(D;i14i8^{fB9N@ib3>_(heLSsks zRg`>TtQFgoJjiuZuKrsN=F$6*Z>N0txFexu10UZ-B{$2ywS<7dzQ#llO(TgSp^`$-|#Z%HdP|Cr783BAX z;JE)F{l6xTeTg{@s+vbTYk@Pt-DQJhj&QJNn9Oqd|K5~a3Xe^ir?_^cKo_yiU$uCdUCx5cU mCziS)xbTy&H^2Dh@umF->YJyR_^zd%i5vWM!$Np=UH&iIRH2pt diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/fetch.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/fetch.cpython-312.pyc deleted file mode 100644 index b78dfa3f64f1096d76869d5eda3e1dd3ec532927..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28647 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 H!uEdvQZt;K diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/request.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/request.cpython-312.pyc deleted file mode 100644 index e5612420b317f968d3072ffa19e723ed45de9f5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1430 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 None: - self.host = host - self.port = port - self.timeout = timeout if isinstance(timeout, float) else 0.0 - self.scheme = "http" - self._closed = True - self._response = None - # ignore these things because we don't - # have control over that stuff - self.proxy = None - self.proxy_config = None - self.blocksize = blocksize - self.source_address = None - self.socket_options = None - self.is_verified = False - - def set_tunnel( - self, - host: str, - port: int | None = 0, - headers: typing.Mapping[str, str] | None = None, - scheme: str = "http", - ) -> None: - pass - - def connect(self) -> None: - pass - - def request( - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - # We know *at least* botocore is depending on the order of the - # first 3 parameters so to be safe we only mark the later ones - # as keyword-only to ensure we have space to extend. - *, - chunked: bool = False, - preload_content: bool = True, - decode_content: bool = True, - enforce_content_length: bool = True, - ) -> None: - self._closed = False - if url.startswith("/"): - if self.port is not None: - port = f":{self.port}" - else: - port = "" - # no scheme / host / port included, make a full url - url = f"{self.scheme}://{self.host}{port}{url}" - request = EmscriptenRequest( - url=url, - method=method, - timeout=self.timeout if self.timeout else 0, - decode_content=decode_content, - ) - request.set_body(body) - if headers: - for k, v in headers.items(): - request.set_header(k, v) - self._response = None - try: - if not preload_content: - self._response = send_streaming_request(request) - if self._response is None: - self._response = send_request(request) - except _TimeoutError as e: - raise TimeoutError(e.message) from e - except _RequestError as e: - raise HTTPException(e.message) from e - - def getresponse(self) -> BaseHTTPResponse: - if self._response is not None: - return EmscriptenHttpResponseWrapper( - internal_response=self._response, - url=self._response.request.url, - connection=self, - ) - else: - raise ResponseNotReady() - - def close(self) -> None: - self._closed = True - self._response = None - - @property - def is_closed(self) -> bool: - """Whether the connection either is brand new or has been previously closed. - If this property is True then both ``is_connected`` and ``has_connected_to_proxy`` - properties must be False. - """ - return self._closed - - @property - def is_connected(self) -> bool: - """Whether the connection is actively connected to any origin (proxy or target)""" - return True - - @property - def has_connected_to_proxy(self) -> bool: - """Whether the connection has successfully connected to its proxy. - This returns False if no proxy is in use. Used to determine whether - errors are coming from the proxy layer or from tunnelling to the target origin. - """ - return False - - -class EmscriptenHTTPSConnection(EmscriptenHTTPConnection): - default_port = port_by_scheme["https"] - # all this is basically ignored, as browser handles https - cert_reqs: int | str | None = None - ca_certs: str | None = None - ca_cert_dir: str | None = None - ca_cert_data: None | str | bytes = None - cert_file: str | None - key_file: str | None - key_password: str | None - ssl_context: typing.Any | None - ssl_version: int | str | None = None - ssl_minimum_version: int | None = None - ssl_maximum_version: int | None = None - assert_hostname: None | str | typing.Literal[False] - assert_fingerprint: str | None = None - - def __init__( - self, - host: str, - port: int = 0, - *, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - blocksize: int = 16384, - socket_options: ( - None | _TYPE_SOCKET_OPTIONS - ) = HTTPConnection.default_socket_options, - proxy: Url | None = None, - proxy_config: ProxyConfig | None = None, - cert_reqs: int | str | None = None, - assert_hostname: None | str | typing.Literal[False] = None, - assert_fingerprint: str | None = None, - server_hostname: str | None = None, - ssl_context: typing.Any | None = None, - ca_certs: str | None = None, - ca_cert_dir: str | None = None, - ca_cert_data: None | str | bytes = None, - ssl_minimum_version: int | None = None, - ssl_maximum_version: int | None = None, - ssl_version: int | str | None = None, # Deprecated - cert_file: str | None = None, - key_file: str | None = None, - key_password: str | None = None, - ) -> None: - super().__init__( - host, - port=port, - timeout=timeout, - source_address=source_address, - blocksize=blocksize, - socket_options=socket_options, - proxy=proxy, - proxy_config=proxy_config, - ) - self.scheme = "https" - - self.key_file = key_file - self.cert_file = cert_file - self.key_password = key_password - self.ssl_context = ssl_context - self.server_hostname = server_hostname - self.assert_hostname = assert_hostname - self.assert_fingerprint = assert_fingerprint - self.ssl_version = ssl_version - self.ssl_minimum_version = ssl_minimum_version - self.ssl_maximum_version = ssl_maximum_version - self.ca_certs = ca_certs and os.path.expanduser(ca_certs) - self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) - self.ca_cert_data = ca_cert_data - - self.cert_reqs = None - - # The browser will automatically verify all requests. - # We have no control over that setting. - self.is_verified = True - - def set_cert( - self, - key_file: str | None = None, - cert_file: str | None = None, - cert_reqs: int | str | None = None, - key_password: str | None = None, - ca_certs: str | None = None, - assert_hostname: None | str | typing.Literal[False] = None, - assert_fingerprint: str | None = None, - ca_cert_dir: str | None = None, - ca_cert_data: None | str | bytes = None, - ) -> None: - pass - - -# verify that this class implements BaseHTTP(s) connection correctly -if typing.TYPE_CHECKING: - _supports_http_protocol: BaseHTTPConnection = EmscriptenHTTPConnection("", 0) - _supports_https_protocol: BaseHTTPSConnection = EmscriptenHTTPSConnection("", 0) diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js b/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js deleted file mode 100644 index faf141e..0000000 --- a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js +++ /dev/null @@ -1,110 +0,0 @@ -let Status = { - SUCCESS_HEADER: -1, - SUCCESS_EOF: -2, - ERROR_TIMEOUT: -3, - ERROR_EXCEPTION: -4, -}; - -let connections = new Map(); -let nextConnectionID = 1; -const encoder = new TextEncoder(); - -self.addEventListener("message", async function (event) { - if (event.data.close) { - let connectionID = event.data.close; - connections.delete(connectionID); - return; - } else if (event.data.getMore) { - let connectionID = event.data.getMore; - let { curOffset, value, reader, intBuffer, byteBuffer } = - connections.get(connectionID); - // if we still have some in buffer, then just send it back straight away - if (!value || curOffset >= value.length) { - // read another buffer if required - try { - let readResponse = await reader.read(); - - if (readResponse.done) { - // read everything - clear connection and return - connections.delete(connectionID); - Atomics.store(intBuffer, 0, Status.SUCCESS_EOF); - Atomics.notify(intBuffer, 0); - // finished reading successfully - // return from event handler - return; - } - curOffset = 0; - connections.get(connectionID).value = readResponse.value; - value = readResponse.value; - } catch (error) { - console.log("Request exception:", error); - let errorBytes = encoder.encode(error.message); - let written = errorBytes.length; - byteBuffer.set(errorBytes); - intBuffer[1] = written; - Atomics.store(intBuffer, 0, Status.ERROR_EXCEPTION); - Atomics.notify(intBuffer, 0); - } - } - - // send as much buffer as we can - let curLen = value.length - curOffset; - if (curLen > byteBuffer.length) { - curLen = byteBuffer.length; - } - byteBuffer.set(value.subarray(curOffset, curOffset + curLen), 0); - - Atomics.store(intBuffer, 0, curLen); // store current length in bytes - Atomics.notify(intBuffer, 0); - curOffset += curLen; - connections.get(connectionID).curOffset = curOffset; - - return; - } else { - // start fetch - let connectionID = nextConnectionID; - nextConnectionID += 1; - const intBuffer = new Int32Array(event.data.buffer); - const byteBuffer = new Uint8Array(event.data.buffer, 8); - try { - const response = await fetch(event.data.url, event.data.fetchParams); - // return the headers first via textencoder - var headers = []; - for (const pair of response.headers.entries()) { - headers.push([pair[0], pair[1]]); - } - let headerObj = { - headers: headers, - status: response.status, - connectionID, - }; - const headerText = JSON.stringify(headerObj); - let headerBytes = encoder.encode(headerText); - let written = headerBytes.length; - byteBuffer.set(headerBytes); - intBuffer[1] = written; - // make a connection - connections.set(connectionID, { - reader: response.body.getReader(), - intBuffer: intBuffer, - byteBuffer: byteBuffer, - value: undefined, - curOffset: 0, - }); - // set header ready - Atomics.store(intBuffer, 0, Status.SUCCESS_HEADER); - Atomics.notify(intBuffer, 0); - // all fetching after this goes through a new postmessage call with getMore - // this allows for parallel requests - } catch (error) { - console.log("Request exception:", error); - let errorBytes = encoder.encode(error.message); - let written = errorBytes.length; - byteBuffer.set(errorBytes); - intBuffer[1] = written; - Atomics.store(intBuffer, 0, Status.ERROR_EXCEPTION); - Atomics.notify(intBuffer, 0); - } - } -}); -self.postMessage({ inited: true }); diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/fetch.py b/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/fetch.py deleted file mode 100644 index 612cfdd..0000000 --- a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/fetch.py +++ /dev/null @@ -1,726 +0,0 @@ -""" -Support for streaming http requests in emscripten. - -A few caveats - - -If your browser (or Node.js) has WebAssembly JavaScript Promise Integration enabled -https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md -*and* you launch pyodide using `pyodide.runPythonAsync`, this will fetch data using the -JavaScript asynchronous fetch api (wrapped via `pyodide.ffi.call_sync`). In this case -timeouts and streaming should just work. - -Otherwise, it uses a combination of XMLHttpRequest and a web-worker for streaming. - -This approach has several caveats: - -Firstly, you can't do streaming http in the main UI thread, because atomics.wait isn't allowed. -Streaming only works if you're running pyodide in a web worker. - -Secondly, this uses an extra web worker and SharedArrayBuffer to do the asynchronous fetch -operation, so it requires that you have crossOriginIsolation enabled, by serving over https -(or from localhost) with the two headers below set: - - Cross-Origin-Opener-Policy: same-origin - Cross-Origin-Embedder-Policy: require-corp - -You can tell if cross origin isolation is successfully enabled by looking at the global crossOriginIsolated variable in -JavaScript console. If it isn't, streaming requests will fallback to XMLHttpRequest, i.e. getting the whole -request into a buffer and then returning it. it shows a warning in the JavaScript console in this case. - -Finally, the webworker which does the streaming fetch is created on initial import, but will only be started once -control is returned to javascript. Call `await wait_for_streaming_ready()` to wait for streaming fetch. - -NB: in this code, there are a lot of JavaScript objects. They are named js_* -to make it clear what type of object they are. -""" - -from __future__ import annotations - -import io -import json -from email.parser import Parser -from importlib.resources import files -from typing import TYPE_CHECKING, Any - -import js # type: ignore[import-not-found] -from pyodide.ffi import ( # type: ignore[import-not-found] - JsArray, - JsException, - JsProxy, - to_js, -) - -if TYPE_CHECKING: - from typing_extensions import Buffer - -from .request import EmscriptenRequest -from .response import EmscriptenResponse - -""" -There are some headers that trigger unintended CORS preflight requests. -See also https://github.com/koenvo/pyodide-http/issues/22 -""" -HEADERS_TO_IGNORE = ("user-agent",) - -SUCCESS_HEADER = -1 -SUCCESS_EOF = -2 -ERROR_TIMEOUT = -3 -ERROR_EXCEPTION = -4 - - -class _RequestError(Exception): - def __init__( - self, - message: str | None = None, - *, - request: EmscriptenRequest | None = None, - response: EmscriptenResponse | None = None, - ): - self.request = request - self.response = response - self.message = message - super().__init__(self.message) - - -class _StreamingError(_RequestError): - pass - - -class _TimeoutError(_RequestError): - pass - - -def _obj_from_dict(dict_val: dict[str, Any]) -> JsProxy: - return to_js(dict_val, dict_converter=js.Object.fromEntries) - - -class _ReadStream(io.RawIOBase): - def __init__( - self, - int_buffer: JsArray, - byte_buffer: JsArray, - timeout: float, - worker: JsProxy, - connection_id: int, - request: EmscriptenRequest, - ): - self.int_buffer = int_buffer - self.byte_buffer = byte_buffer - self.read_pos = 0 - self.read_len = 0 - self.connection_id = connection_id - self.worker = worker - self.timeout = int(1000 * timeout) if timeout > 0 else None - self.is_live = True - self._is_closed = False - self.request: EmscriptenRequest | None = request - - def __del__(self) -> None: - self.close() - - # this is compatible with _base_connection - def is_closed(self) -> bool: - return self._is_closed - - # for compatibility with RawIOBase - @property - def closed(self) -> bool: - return self.is_closed() - - def close(self) -> None: - if self.is_closed(): - return - self.read_len = 0 - self.read_pos = 0 - self.int_buffer = None - self.byte_buffer = None - self._is_closed = True - self.request = None - if self.is_live: - self.worker.postMessage(_obj_from_dict({"close": self.connection_id})) - self.is_live = False - super().close() - - def readable(self) -> bool: - return True - - def writable(self) -> bool: - return False - - def seekable(self) -> bool: - return False - - def readinto(self, byte_obj: Buffer) -> int: - if not self.int_buffer: - raise _StreamingError( - "No buffer for stream in _ReadStream.readinto", - request=self.request, - response=None, - ) - if self.read_len == 0: - # wait for the worker to send something - js.Atomics.store(self.int_buffer, 0, ERROR_TIMEOUT) - self.worker.postMessage(_obj_from_dict({"getMore": self.connection_id})) - if ( - js.Atomics.wait(self.int_buffer, 0, ERROR_TIMEOUT, self.timeout) - == "timed-out" - ): - raise _TimeoutError - data_len = self.int_buffer[0] - if data_len > 0: - self.read_len = data_len - self.read_pos = 0 - elif data_len == ERROR_EXCEPTION: - string_len = self.int_buffer[1] - # decode the error string - js_decoder = js.TextDecoder.new() - json_str = js_decoder.decode(self.byte_buffer.slice(0, string_len)) - raise _StreamingError( - f"Exception thrown in fetch: {json_str}", - request=self.request, - response=None, - ) - else: - # EOF, free the buffers and return zero - # and free the request - self.is_live = False - self.close() - return 0 - # copy from int32array to python bytes - ret_length = min(self.read_len, len(memoryview(byte_obj))) - subarray = self.byte_buffer.subarray( - self.read_pos, self.read_pos + ret_length - ).to_py() - memoryview(byte_obj)[0:ret_length] = subarray - self.read_len -= ret_length - self.read_pos += ret_length - return ret_length - - -class _StreamingFetcher: - def __init__(self) -> None: - # make web-worker and data buffer on startup - self.streaming_ready = False - streaming_worker_code = ( - files(__package__) - .joinpath("emscripten_fetch_worker.js") - .read_text(encoding="utf-8") - ) - js_data_blob = js.Blob.new( - to_js([streaming_worker_code], create_pyproxies=False), - _obj_from_dict({"type": "application/javascript"}), - ) - - def promise_resolver(js_resolve_fn: JsProxy, js_reject_fn: JsProxy) -> None: - def onMsg(e: JsProxy) -> None: - self.streaming_ready = True - js_resolve_fn(e) - - def onErr(e: JsProxy) -> None: - js_reject_fn(e) # Defensive: never happens in ci - - self.js_worker.onmessage = onMsg - self.js_worker.onerror = onErr - - js_data_url = js.URL.createObjectURL(js_data_blob) - self.js_worker = js.globalThis.Worker.new(js_data_url) - self.js_worker_ready_promise = js.globalThis.Promise.new(promise_resolver) - - def send(self, request: EmscriptenRequest) -> EmscriptenResponse: - headers = { - k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE - } - - body = request.body - fetch_data = {"headers": headers, "body": to_js(body), "method": request.method} - # start the request off in the worker - timeout = int(1000 * request.timeout) if request.timeout > 0 else None - js_shared_buffer = js.SharedArrayBuffer.new(1048576) - js_int_buffer = js.Int32Array.new(js_shared_buffer) - js_byte_buffer = js.Uint8Array.new(js_shared_buffer, 8) - - js.Atomics.store(js_int_buffer, 0, ERROR_TIMEOUT) - js.Atomics.notify(js_int_buffer, 0) - js_absolute_url = js.URL.new(request.url, js.location).href - self.js_worker.postMessage( - _obj_from_dict( - { - "buffer": js_shared_buffer, - "url": js_absolute_url, - "fetchParams": fetch_data, - } - ) - ) - # wait for the worker to send something - js.Atomics.wait(js_int_buffer, 0, ERROR_TIMEOUT, timeout) - if js_int_buffer[0] == ERROR_TIMEOUT: - raise _TimeoutError( - "Timeout connecting to streaming request", - request=request, - response=None, - ) - elif js_int_buffer[0] == SUCCESS_HEADER: - # got response - # header length is in second int of intBuffer - string_len = js_int_buffer[1] - # decode the rest to a JSON string - js_decoder = js.TextDecoder.new() - # this does a copy (the slice) because decode can't work on shared array - # for some silly reason - json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len)) - # get it as an object - response_obj = json.loads(json_str) - return EmscriptenResponse( - request=request, - status_code=response_obj["status"], - headers=response_obj["headers"], - body=_ReadStream( - js_int_buffer, - js_byte_buffer, - request.timeout, - self.js_worker, - response_obj["connectionID"], - request, - ), - ) - elif js_int_buffer[0] == ERROR_EXCEPTION: - string_len = js_int_buffer[1] - # decode the error string - js_decoder = js.TextDecoder.new() - json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len)) - raise _StreamingError( - f"Exception thrown in fetch: {json_str}", request=request, response=None - ) - else: - raise _StreamingError( - f"Unknown status from worker in fetch: {js_int_buffer[0]}", - request=request, - response=None, - ) - - -class _JSPIReadStream(io.RawIOBase): - """ - A read stream that uses pyodide.ffi.run_sync to read from a JavaScript fetch - response. This requires support for WebAssembly JavaScript Promise Integration - in the containing browser, and for pyodide to be launched via runPythonAsync. - - :param js_read_stream: - The JavaScript stream reader - - :param timeout: - Timeout in seconds - - :param request: - The request we're handling - - :param response: - The response this stream relates to - - :param js_abort_controller: - A JavaScript AbortController object, used for timeouts - """ - - def __init__( - self, - js_read_stream: Any, - timeout: float, - request: EmscriptenRequest, - response: EmscriptenResponse, - js_abort_controller: Any, # JavaScript AbortController for timeouts - ): - self.js_read_stream = js_read_stream - self.timeout = timeout - self._is_closed = False - self._is_done = False - self.request: EmscriptenRequest | None = request - self.response: EmscriptenResponse | None = response - self.current_buffer = None - self.current_buffer_pos = 0 - self.js_abort_controller = js_abort_controller - - def __del__(self) -> None: - self.close() - - # this is compatible with _base_connection - def is_closed(self) -> bool: - return self._is_closed - - # for compatibility with RawIOBase - @property - def closed(self) -> bool: - return self.is_closed() - - def close(self) -> None: - if self.is_closed(): - return - self.read_len = 0 - self.read_pos = 0 - self.js_read_stream.cancel() - self.js_read_stream = None - self._is_closed = True - self._is_done = True - self.request = None - self.response = None - super().close() - - def readable(self) -> bool: - return True - - def writable(self) -> bool: - return False - - def seekable(self) -> bool: - return False - - def _get_next_buffer(self) -> bool: - result_js = _run_sync_with_timeout( - self.js_read_stream.read(), - self.timeout, - self.js_abort_controller, - request=self.request, - response=self.response, - ) - if result_js.done: - self._is_done = True - return False - else: - self.current_buffer = result_js.value.to_py() - self.current_buffer_pos = 0 - return True - - def readinto(self, byte_obj: Buffer) -> int: - if self.current_buffer is None: - if not self._get_next_buffer() or self.current_buffer is None: - self.close() - return 0 - ret_length = min( - len(byte_obj), len(self.current_buffer) - self.current_buffer_pos - ) - byte_obj[0:ret_length] = self.current_buffer[ - self.current_buffer_pos : self.current_buffer_pos + ret_length - ] - self.current_buffer_pos += ret_length - if self.current_buffer_pos == len(self.current_buffer): - self.current_buffer = None - return ret_length - - -# check if we are in a worker or not -def is_in_browser_main_thread() -> bool: - return hasattr(js, "window") and hasattr(js, "self") and js.self == js.window - - -def is_cross_origin_isolated() -> bool: - return hasattr(js, "crossOriginIsolated") and js.crossOriginIsolated - - -def is_in_node() -> bool: - return ( - hasattr(js, "process") - and hasattr(js.process, "release") - and hasattr(js.process.release, "name") - and js.process.release.name == "node" - ) - - -def is_worker_available() -> bool: - return hasattr(js, "Worker") and hasattr(js, "Blob") - - -_fetcher: _StreamingFetcher | None = None - -if is_worker_available() and ( - (is_cross_origin_isolated() and not is_in_browser_main_thread()) - and (not is_in_node()) -): - _fetcher = _StreamingFetcher() -else: - _fetcher = None - - -NODE_JSPI_ERROR = ( - "urllib3 only works in Node.js with pyodide.runPythonAsync" - " and requires the flag --experimental-wasm-stack-switching in " - " versions of node <24." -) - - -def send_streaming_request(request: EmscriptenRequest) -> EmscriptenResponse | None: - if has_jspi(): - return send_jspi_request(request, True) - elif is_in_node(): - raise _RequestError( - message=NODE_JSPI_ERROR, - request=request, - response=None, - ) - - if _fetcher and streaming_ready(): - return _fetcher.send(request) - else: - _show_streaming_warning() - return None - - -_SHOWN_TIMEOUT_WARNING = False - - -def _show_timeout_warning() -> None: - global _SHOWN_TIMEOUT_WARNING - if not _SHOWN_TIMEOUT_WARNING: - _SHOWN_TIMEOUT_WARNING = True - message = "Warning: Timeout is not available on main browser thread" - js.console.warn(message) - - -_SHOWN_STREAMING_WARNING = False - - -def _show_streaming_warning() -> None: - global _SHOWN_STREAMING_WARNING - if not _SHOWN_STREAMING_WARNING: - _SHOWN_STREAMING_WARNING = True - message = "Can't stream HTTP requests because: \n" - if not is_cross_origin_isolated(): - message += " Page is not cross-origin isolated\n" - if is_in_browser_main_thread(): - message += " Python is running in main browser thread\n" - if not is_worker_available(): - message += " Worker or Blob classes are not available in this environment." # Defensive: this is always False in browsers that we test in - if streaming_ready() is False: - message += """ Streaming fetch worker isn't ready. If you want to be sure that streaming fetch -is working, you need to call: 'await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()`""" - from js import console - - console.warn(message) - - -def send_request(request: EmscriptenRequest) -> EmscriptenResponse: - if has_jspi(): - return send_jspi_request(request, False) - elif is_in_node(): - raise _RequestError( - message=NODE_JSPI_ERROR, - request=request, - response=None, - ) - try: - js_xhr = js.XMLHttpRequest.new() - - if not is_in_browser_main_thread(): - js_xhr.responseType = "arraybuffer" - if request.timeout: - js_xhr.timeout = int(request.timeout * 1000) - else: - js_xhr.overrideMimeType("text/plain; charset=ISO-8859-15") - if request.timeout: - # timeout isn't available on the main thread - show a warning in console - # if it is set - _show_timeout_warning() - - js_xhr.open(request.method, request.url, False) - for name, value in request.headers.items(): - if name.lower() not in HEADERS_TO_IGNORE: - js_xhr.setRequestHeader(name, value) - - js_xhr.send(to_js(request.body)) - - headers = dict(Parser().parsestr(js_xhr.getAllResponseHeaders())) - - if not is_in_browser_main_thread(): - body = js_xhr.response.to_py().tobytes() - else: - body = js_xhr.response.encode("ISO-8859-15") - return EmscriptenResponse( - status_code=js_xhr.status, headers=headers, body=body, request=request - ) - except JsException as err: - if err.name == "TimeoutError": - raise _TimeoutError(err.message, request=request) - elif err.name == "NetworkError": - raise _RequestError(err.message, request=request) - else: - # general http error - raise _RequestError(err.message, request=request) - - -def send_jspi_request( - request: EmscriptenRequest, streaming: bool -) -> EmscriptenResponse: - """ - Send a request using WebAssembly JavaScript Promise Integration - to wrap the asynchronous JavaScript fetch api (experimental). - - :param request: - Request to send - - :param streaming: - Whether to stream the response - - :return: The response object - :rtype: EmscriptenResponse - """ - timeout = request.timeout - js_abort_controller = js.AbortController.new() - headers = {k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE} - req_body = request.body - fetch_data = { - "headers": headers, - "body": to_js(req_body), - "method": request.method, - "signal": js_abort_controller.signal, - } - # Node.js returns the whole response (unlike opaqueredirect in browsers), - # so urllib3 can set `redirect: manual` to control redirects itself. - # https://stackoverflow.com/a/78524615 - if _is_node_js(): - fetch_data["redirect"] = "manual" - # Call JavaScript fetch (async api, returns a promise) - fetcher_promise_js = js.fetch(request.url, _obj_from_dict(fetch_data)) - # Now suspend WebAssembly until we resolve that promise - # or time out. - response_js = _run_sync_with_timeout( - fetcher_promise_js, - timeout, - js_abort_controller, - request=request, - response=None, - ) - headers = {} - header_iter = response_js.headers.entries() - while True: - iter_value_js = header_iter.next() - if getattr(iter_value_js, "done", False): - break - else: - headers[str(iter_value_js.value[0])] = str(iter_value_js.value[1]) - status_code = response_js.status - body: bytes | io.RawIOBase = b"" - - response = EmscriptenResponse( - status_code=status_code, headers=headers, body=b"", request=request - ) - if streaming: - # get via inputstream - if response_js.body is not None: - # get a reader from the fetch response - body_stream_js = response_js.body.getReader() - body = _JSPIReadStream( - body_stream_js, timeout, request, response, js_abort_controller - ) - else: - # get directly via arraybuffer - # n.b. this is another async JavaScript call. - body = _run_sync_with_timeout( - response_js.arrayBuffer(), - timeout, - js_abort_controller, - request=request, - response=response, - ).to_py() - response.body = body - return response - - -def _run_sync_with_timeout( - promise: Any, - timeout: float, - js_abort_controller: Any, - request: EmscriptenRequest | None, - response: EmscriptenResponse | None, -) -> Any: - """ - Await a JavaScript promise synchronously with a timeout which is implemented - via the AbortController - - :param promise: - Javascript promise to await - - :param timeout: - Timeout in seconds - - :param js_abort_controller: - A JavaScript AbortController object, used on timeout - - :param request: - The request being handled - - :param response: - The response being handled (if it exists yet) - - :raises _TimeoutError: If the request times out - :raises _RequestError: If the request raises a JavaScript exception - - :return: The result of awaiting the promise. - """ - timer_id = None - if timeout > 0: - timer_id = js.setTimeout( - js_abort_controller.abort.bind(js_abort_controller), int(timeout * 1000) - ) - try: - from pyodide.ffi import run_sync - - # run_sync here uses WebAssembly JavaScript Promise Integration to - # suspend python until the JavaScript promise resolves. - return run_sync(promise) - except JsException as err: - if err.name == "AbortError": - raise _TimeoutError( - message="Request timed out", request=request, response=response - ) - else: - raise _RequestError(message=err.message, request=request, response=response) - finally: - if timer_id is not None: - js.clearTimeout(timer_id) - - -def has_jspi() -> bool: - """ - Return true if jspi can be used. - - This requires both browser support and also WebAssembly - to be in the correct state - i.e. that the javascript - call into python was async not sync. - - :return: True if jspi can be used. - :rtype: bool - """ - try: - from pyodide.ffi import can_run_sync, run_sync # noqa: F401 - - return bool(can_run_sync()) - except ImportError: - return False - - -def _is_node_js() -> bool: - """ - Check if we are in Node.js. - - :return: True if we are in Node.js. - :rtype: bool - """ - return ( - hasattr(js, "process") - and hasattr(js.process, "release") - # According to the Node.js documentation, the release name is always "node". - and js.process.release.name == "node" - ) - - -def streaming_ready() -> bool | None: - if _fetcher: - return _fetcher.streaming_ready - else: - return None # no fetcher, return None to signify that - - -async def wait_for_streaming_ready() -> bool: - if _fetcher: - await _fetcher.js_worker_ready_promise - return True - else: - return False diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/request.py b/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/request.py deleted file mode 100644 index e692e69..0000000 --- a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/request.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass, field - -from ..._base_connection import _TYPE_BODY - - -@dataclass -class EmscriptenRequest: - method: str - url: str - params: dict[str, str] | None = None - body: _TYPE_BODY | None = None - headers: dict[str, str] = field(default_factory=dict) - timeout: float = 0 - decode_content: bool = True - - def set_header(self, name: str, value: str) -> None: - self.headers[name.capitalize()] = value - - def set_body(self, body: _TYPE_BODY | None) -> None: - self.body = body diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/response.py b/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/response.py deleted file mode 100644 index ec1e1db..0000000 --- a/venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/response.py +++ /dev/null @@ -1,281 +0,0 @@ -from __future__ import annotations - -import json as _json -import logging -import typing -from contextlib import contextmanager -from dataclasses import dataclass -from http.client import HTTPException as HTTPException -from io import BytesIO, IOBase - -from ...exceptions import InvalidHeader, TimeoutError -from ...response import BaseHTTPResponse -from ...util.retry import Retry -from .request import EmscriptenRequest - -if typing.TYPE_CHECKING: - from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection - -log = logging.getLogger(__name__) - - -@dataclass -class EmscriptenResponse: - status_code: int - headers: dict[str, str] - body: IOBase | bytes - request: EmscriptenRequest - - -class EmscriptenHttpResponseWrapper(BaseHTTPResponse): - def __init__( - self, - internal_response: EmscriptenResponse, - url: str | None = None, - connection: BaseHTTPConnection | BaseHTTPSConnection | None = None, - ): - self._pool = None # set by pool class - self._body = None - self._uncached_read_occurred = False - self._response = internal_response - self._url = url - self._connection = connection - self._closed = False - super().__init__( - headers=internal_response.headers, - status=internal_response.status_code, - request_url=url, - version=0, - version_string="HTTP/?", - reason="", - decode_content=True, - ) - self.length_remaining = self._init_length(self._response.request.method) - self.length_is_certain = False - - @property - def url(self) -> str | None: - return self._url - - @url.setter - def url(self, url: str | None) -> None: - self._url = url - - @property - def connection(self) -> BaseHTTPConnection | BaseHTTPSConnection | None: - return self._connection - - @property - def retries(self) -> Retry | None: - return self._retries - - @retries.setter - def retries(self, retries: Retry | None) -> None: - # Override the request_url if retries has a redirect location. - self._retries = retries - - def stream( - self, amt: int | None = 2**16, decode_content: bool | None = None - ) -> typing.Generator[bytes]: - """ - A generator wrapper for the read() method. A call will block until - ``amt`` bytes have been read from the connection or until the - connection is closed. - - :param amt: - How much of the content to read. The generator will return up to - much data per iteration, but may return less. This is particularly - likely when using compressed data. However, the empty string will - never be returned. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - """ - while True: - data = self.read(amt=amt, decode_content=decode_content) - - if data: - yield data - else: - break - - def _init_length(self, request_method: str | None) -> int | None: - length: int | None - content_length: str | None = self.headers.get("content-length") - - if content_length is not None: - try: - # RFC 7230 section 3.3.2 specifies multiple content lengths can - # be sent in a single Content-Length header - # (e.g. Content-Length: 42, 42). This line ensures the values - # are all valid ints and that as long as the `set` length is 1, - # all values are the same. Otherwise, the header is invalid. - lengths = {int(val) for val in content_length.split(",")} - if len(lengths) > 1: - raise InvalidHeader( - "Content-Length contained multiple " - "unmatching values (%s)" % content_length - ) - length = lengths.pop() - except ValueError: - length = None - else: - if length < 0: - length = None - - else: # if content_length is None - length = None - - # Check for responses that shouldn't include a body - if ( - self.status in (204, 304) - or 100 <= self.status < 200 - or request_method == "HEAD" - ): - length = 0 - - return length - - def read( - self, - amt: int | None = None, - decode_content: bool | None = None, # ignored because browser decodes always - cache_content: bool = False, - ) -> bytes: - if ( - self._closed - or self._response is None - or (isinstance(self._response.body, IOBase) and self._response.body.closed) - ): - return b"" - - with self._error_catcher(): - # body has been preloaded as a string by XmlHttpRequest - if not isinstance(self._response.body, IOBase): - self.length_remaining = len(self._response.body) - self.length_is_certain = True - # wrap body in IOStream - self._response.body = BytesIO(self._response.body) - if amt is not None and amt >= 0: - # don't cache partial content - cache_content = False - data = self._response.body.read(amt) - self._uncached_read_occurred = True - else: # read all we can (and cache it) - data = self._response.body.read() - if cache_content and not self._uncached_read_occurred: - self._body = data - else: - self._uncached_read_occurred = True - if self.length_remaining is not None: - self.length_remaining = max(self.length_remaining - len(data), 0) - if len(data) == 0 or ( - self.length_is_certain and self.length_remaining == 0 - ): - # definitely finished reading, close response stream - self._response.body.close() - return typing.cast(bytes, data) - - def read_chunked( - self, - amt: int | None = None, - decode_content: bool | None = None, - ) -> typing.Generator[bytes]: - # chunked is handled by browser - while True: - bytes = self.read(amt, decode_content) - if not bytes: - break - yield bytes - - def release_conn(self) -> None: - if not self._pool or not self._connection: - return None - - self._pool._put_conn(self._connection) - self._connection = None - - def drain_conn(self) -> None: - self.close() - - @property - def data(self) -> bytes: - if self._body: - return self._body - else: - return self.read(cache_content=True) - - def json(self) -> typing.Any: - """ - Deserializes the body of the HTTP response as a Python object. - - The body of the HTTP response must be encoded using UTF-8, as per - `RFC 8529 Section 8.1 `_. - - To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to - your custom decoder instead. - - If the body of the HTTP response is not decodable to UTF-8, a - `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a - valid JSON document, a `json.JSONDecodeError` will be raised. - - Read more :ref:`here `. - - :returns: The body of the HTTP response as a Python object. - """ - data = self.data.decode("utf-8") - return _json.loads(data) - - def close(self) -> None: - if not self._closed: - if isinstance(self._response.body, IOBase): - self._response.body.close() - if self._connection: - self._connection.close() - self._connection = None - self._closed = True - - @contextmanager - def _error_catcher(self) -> typing.Generator[None]: - """ - Catch Emscripten specific exceptions thrown by fetch.py, - instead re-raising urllib3 variants, so that low-level exceptions - are not leaked in the high-level api. - - On exit, release the connection back to the pool. - """ - from .fetch import _RequestError, _TimeoutError # avoid circular import - - clean_exit = False - - try: - yield - # If no exception is thrown, we should avoid cleaning up - # unnecessarily. - clean_exit = True - except _TimeoutError as e: - raise TimeoutError(str(e)) - except _RequestError as e: - raise HTTPException(str(e)) - finally: - # If we didn't terminate cleanly, we need to throw away our - # connection. - if not clean_exit: - # The response may not be closed but we're not going to use it - # anymore so close it now - if ( - isinstance(self._response.body, IOBase) - and not self._response.body.closed - ): - self._response.body.close() - # release the connection back to the pool - self.release_conn() - else: - # If we have read everything from the response stream, - # return the connection back to the pool. - if ( - isinstance(self._response.body, IOBase) - and self._response.body.closed - ): - self.release_conn() diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/pyopenssl.py b/venv/lib/python3.12/site-packages/urllib3/contrib/pyopenssl.py index f06b859..1ed214b 100644 --- a/venv/lib/python3.12/site-packages/urllib3/contrib/pyopenssl.py +++ b/venv/lib/python3.12/site-packages/urllib3/contrib/pyopenssl.py @@ -1,17 +1,17 @@ """ -Module for using pyOpenSSL as a TLS backend. This module was relevant before -the standard library ``ssl`` module supported SNI, but now that we've dropped -support for Python 2.7 all relevant Python versions support SNI so -**this module is no longer recommended**. +TLS with SNI_-support for Python 2. Follow these instructions if you would +like to verify TLS certificates in Python 2. Note, the default libraries do +*not* do certificate checking; you need to do additional work to validate +certificates yourself. This needs the following packages installed: -* `pyOpenSSL`_ (tested with 19.0.0) -* `cryptography`_ (minimum 2.3, from pyopenssl) -* `idna`_ (minimum 2.1, from cryptography) +* `pyOpenSSL`_ (tested with 16.0.0) +* `cryptography`_ (minimum 1.3.4, from pyopenssl) +* `idna`_ (minimum 2.0, from cryptography) -However, pyOpenSSL depends on cryptography, so while we use all three directly here we -end up having relatively few packages required. +However, pyopenssl depends on cryptography, which depends on idna, so while we +use all three directly here we end up having relatively few packages required. You can install them with the following command: @@ -33,45 +33,75 @@ like this: except ImportError: pass +Now you can use :mod:`urllib3` as you normally would, and it will support SNI +when the required modules are installed. + +Activating this module also has the positive side effect of disabling SSL/TLS +compression in Python 2 (see `CRIME attack`_). + +.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication +.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) .. _pyopenssl: https://www.pyopenssl.org .. _cryptography: https://cryptography.io .. _idna: https://github.com/kjd/idna """ +from __future__ import absolute_import -from __future__ import annotations - -import OpenSSL.SSL # type: ignore[import-not-found] +import OpenSSL.crypto +import OpenSSL.SSL from cryptography import x509 +from cryptography.hazmat.backends.openssl import backend as openssl_backend try: - from cryptography.x509 import UnsupportedExtension # type: ignore[attr-defined] + from cryptography.x509 import UnsupportedExtension except ImportError: # UnsupportedExtension is gone in cryptography >= 2.1.0 - class UnsupportedExtension(Exception): # type: ignore[no-redef] + class UnsupportedExtension(Exception): pass +from io import BytesIO +from socket import error as SocketError +from socket import timeout + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + import logging import ssl -import typing -from io import BytesIO -from socket import socket as socket_cls +import sys +import warnings from .. import util +from ..packages import six +from ..util.ssl_ import PROTOCOL_TLS_CLIENT -if typing.TYPE_CHECKING: - from OpenSSL.crypto import X509 # type: ignore[import-not-found] - +warnings.warn( + "'urllib3.contrib.pyopenssl' module is deprecated and will be removed " + "in a future release of urllib3 2.x. Read more in this issue: " + "https://github.com/urllib3/urllib3/issues/2680", + category=DeprecationWarning, + stacklevel=2, +) __all__ = ["inject_into_urllib3", "extract_from_urllib3"] +# SNI always works. +HAS_SNI = True + # Map from urllib3 to PyOpenSSL compatible parameter-values. -_openssl_versions: dict[int, int] = { - util.ssl_.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] - util.ssl_.PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] +_openssl_versions = { + util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, + PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, } +if hasattr(ssl, "PROTOCOL_SSLv3") and hasattr(OpenSSL.SSL, "SSLv3_METHOD"): + _openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD + if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD @@ -85,77 +115,43 @@ _stdlib_to_openssl_verify = { ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, } -_openssl_to_stdlib_verify = {v: k for k, v in _stdlib_to_openssl_verify.items()} - -# The SSLvX values are the most likely to be missing in the future -# but we check them all just to be sure. -_OP_NO_SSLv2_OR_SSLv3: int = getattr(OpenSSL.SSL, "OP_NO_SSLv2", 0) | getattr( - OpenSSL.SSL, "OP_NO_SSLv3", 0 -) -_OP_NO_TLSv1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1", 0) -_OP_NO_TLSv1_1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_1", 0) -_OP_NO_TLSv1_2: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_2", 0) -_OP_NO_TLSv1_3: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_3", 0) - -_openssl_to_ssl_minimum_version: dict[int, int] = { - ssl.TLSVersion.MINIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3, - ssl.TLSVersion.TLSv1: _OP_NO_SSLv2_OR_SSLv3, - ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1, - ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1, - ssl.TLSVersion.TLSv1_3: ( - _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 - ), - ssl.TLSVersion.MAXIMUM_SUPPORTED: ( - _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 - ), -} -_openssl_to_ssl_maximum_version: dict[int, int] = { - ssl.TLSVersion.MINIMUM_SUPPORTED: ( - _OP_NO_SSLv2_OR_SSLv3 - | _OP_NO_TLSv1 - | _OP_NO_TLSv1_1 - | _OP_NO_TLSv1_2 - | _OP_NO_TLSv1_3 - ), - ssl.TLSVersion.TLSv1: ( - _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3 - ), - ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3, - ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_3, - ssl.TLSVersion.TLSv1_3: _OP_NO_SSLv2_OR_SSLv3, - ssl.TLSVersion.MAXIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3, -} +_openssl_to_stdlib_verify = dict((v, k) for k, v in _stdlib_to_openssl_verify.items()) # OpenSSL will only write 16K at a time SSL_WRITE_BLOCKSIZE = 16384 +orig_util_HAS_SNI = util.HAS_SNI orig_util_SSLContext = util.ssl_.SSLContext log = logging.getLogger(__name__) -def inject_into_urllib3() -> None: +def inject_into_urllib3(): "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." _validate_dependencies_met() - util.SSLContext = PyOpenSSLContext # type: ignore[assignment] - util.ssl_.SSLContext = PyOpenSSLContext # type: ignore[assignment] + util.SSLContext = PyOpenSSLContext + util.ssl_.SSLContext = PyOpenSSLContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI util.IS_PYOPENSSL = True util.ssl_.IS_PYOPENSSL = True -def extract_from_urllib3() -> None: +def extract_from_urllib3(): "Undo monkey-patching by :func:`inject_into_urllib3`." util.SSLContext = orig_util_SSLContext util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI util.IS_PYOPENSSL = False util.ssl_.IS_PYOPENSSL = False -def _validate_dependencies_met() -> None: +def _validate_dependencies_met(): """ Verifies that PyOpenSSL's package-level dependencies have been met. Throws `ImportError` if they are not met. @@ -181,7 +177,7 @@ def _validate_dependencies_met() -> None: ) -def _dnsname_to_stdlib(name: str) -> str | None: +def _dnsname_to_stdlib(name): """ Converts a dNSName SubjectAlternativeName field to the form used by the standard library on the given Python version. @@ -195,7 +191,7 @@ def _dnsname_to_stdlib(name: str) -> str | None: the name given should be skipped. """ - def idna_encode(name: str) -> bytes | None: + def idna_encode(name): """ Borrowed wholesale from the Python Cryptography Project. It turns out that we can't just safely call `idna.encode`: it can explode for @@ -204,7 +200,7 @@ def _dnsname_to_stdlib(name: str) -> str | None: import idna try: - for prefix in ["*.", "."]: + for prefix in [u"*.", u"."]: if name.startswith(prefix): name = name[len(prefix) :] return prefix.encode("ascii") + idna.encode(name) @@ -216,17 +212,24 @@ def _dnsname_to_stdlib(name: str) -> str | None: if ":" in name: return name - encoded_name = idna_encode(name) - if encoded_name is None: + name = idna_encode(name) + if name is None: return None - return encoded_name.decode("utf-8") + elif sys.version_info >= (3, 0): + name = name.decode("utf-8") + return name -def get_subj_alt_name(peer_cert: X509) -> list[tuple[str, str]]: +def get_subj_alt_name(peer_cert): """ Given an PyOpenSSL certificate, provides all the subject alternative names. """ - cert = peer_cert.to_cryptography() + # Pass the cert to cryptography, which has much better APIs for this. + if hasattr(peer_cert, "to_cryptography"): + cert = peer_cert.to_cryptography() + else: + der = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, peer_cert) + cert = x509.load_der_x509_certificate(der, openssl_backend) # We want to find the SAN extension. Ask Cryptography to locate it (it's # faster than looping in Python) @@ -270,94 +273,93 @@ def get_subj_alt_name(peer_cert: X509) -> list[tuple[str, str]]: return names -class WrappedSocket: - """API-compatibility wrapper for Python OpenSSL's Connection-class.""" +class WrappedSocket(object): + """API-compatibility wrapper for Python OpenSSL's Connection-class. - def __init__( - self, - connection: OpenSSL.SSL.Connection, - socket: socket_cls, - suppress_ragged_eofs: bool = True, - ) -> None: + Note: _makefile_refs, _drop() and _reuse() are needed for the garbage + collector of pypy. + """ + + def __init__(self, connection, socket, suppress_ragged_eofs=True): self.connection = connection self.socket = socket self.suppress_ragged_eofs = suppress_ragged_eofs - self._io_refs = 0 + self._makefile_refs = 0 self._closed = False - def fileno(self) -> int: + def fileno(self): return self.socket.fileno() # Copy-pasted from Python 3.5 source code - def _decref_socketios(self) -> None: - if self._io_refs > 0: - self._io_refs -= 1 + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 if self._closed: self.close() - def recv(self, *args: typing.Any, **kwargs: typing.Any) -> bytes: + def recv(self, *args, **kwargs): try: data = self.connection.recv(*args, **kwargs) except OpenSSL.SSL.SysCallError as e: if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): return b"" else: - raise OSError(e.args[0], str(e)) from e + raise SocketError(str(e)) except OpenSSL.SSL.ZeroReturnError: if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: return b"" else: raise - except OpenSSL.SSL.WantReadError as e: + except OpenSSL.SSL.WantReadError: if not util.wait_for_read(self.socket, self.socket.gettimeout()): - raise TimeoutError("The read operation timed out") from e + raise timeout("The read operation timed out") else: return self.recv(*args, **kwargs) # TLS 1.3 post-handshake authentication except OpenSSL.SSL.Error as e: - raise ssl.SSLError(f"read error: {e!r}") from e + raise ssl.SSLError("read error: %r" % e) else: - return data # type: ignore[no-any-return] + return data - def recv_into(self, *args: typing.Any, **kwargs: typing.Any) -> int: + def recv_into(self, *args, **kwargs): try: - return self.connection.recv_into(*args, **kwargs) # type: ignore[no-any-return] + return self.connection.recv_into(*args, **kwargs) except OpenSSL.SSL.SysCallError as e: if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): return 0 else: - raise OSError(e.args[0], str(e)) from e + raise SocketError(str(e)) except OpenSSL.SSL.ZeroReturnError: if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: return 0 else: raise - except OpenSSL.SSL.WantReadError as e: + except OpenSSL.SSL.WantReadError: if not util.wait_for_read(self.socket, self.socket.gettimeout()): - raise TimeoutError("The read operation timed out") from e + raise timeout("The read operation timed out") else: return self.recv_into(*args, **kwargs) # TLS 1.3 post-handshake authentication except OpenSSL.SSL.Error as e: - raise ssl.SSLError(f"read error: {e!r}") from e + raise ssl.SSLError("read error: %r" % e) - def settimeout(self, timeout: float) -> None: + def settimeout(self, timeout): return self.socket.settimeout(timeout) - def _send_until_done(self, data: bytes) -> int: + def _send_until_done(self, data): while True: try: - return self.connection.send(data) # type: ignore[no-any-return] - except OpenSSL.SSL.WantWriteError as e: + return self.connection.send(data) + except OpenSSL.SSL.WantWriteError: if not util.wait_for_write(self.socket, self.socket.gettimeout()): - raise TimeoutError() from e + raise timeout() continue except OpenSSL.SSL.SysCallError as e: - raise OSError(e.args[0], str(e)) from e + raise SocketError(str(e)) - def sendall(self, data: bytes) -> None: + def sendall(self, data): total_sent = 0 while total_sent < len(data): sent = self._send_until_done( @@ -365,151 +367,135 @@ class WrappedSocket: ) total_sent += sent - def shutdown(self, how: int) -> None: - try: - self.connection.shutdown() - except OpenSSL.SSL.Error as e: - raise ssl.SSLError(f"shutdown error: {e!r}") from e + def shutdown(self): + # FIXME rethrow compatible exceptions should we ever use this + self.connection.shutdown() - def close(self) -> None: - self._closed = True - if self._io_refs <= 0: - self._real_close() + def close(self): + if self._makefile_refs < 1: + try: + self._closed = True + return self.connection.close() + except OpenSSL.SSL.Error: + return + else: + self._makefile_refs -= 1 - def _real_close(self) -> None: - try: - return self.connection.close() # type: ignore[no-any-return] - except OpenSSL.SSL.Error: - return - - def getpeercert( - self, binary_form: bool = False - ) -> dict[str, list[typing.Any]] | None: + def getpeercert(self, binary_form=False): x509 = self.connection.get_peer_certificate() if not x509: - return x509 # type: ignore[no-any-return] + return x509 if binary_form: - return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) # type: ignore[no-any-return] + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) return { - "subject": ((("commonName", x509.get_subject().CN),),), # type: ignore[dict-item] + "subject": ((("commonName", x509.get_subject().CN),),), "subjectAltName": get_subj_alt_name(x509), } - def version(self) -> str: - return self.connection.get_protocol_version_name() # type: ignore[no-any-return] + def version(self): + return self.connection.get_protocol_version_name() - def selected_alpn_protocol(self) -> str | None: - alpn_proto = self.connection.get_alpn_proto_negotiated() - return alpn_proto.decode() if alpn_proto else None + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 -WrappedSocket.makefile = socket_cls.makefile # type: ignore[attr-defined] +if _fileobject: # Platform-specific: Python 2 + + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) + +else: # Platform-specific: Python 3 + makefile = backport_makefile + +WrappedSocket.makefile = makefile -class PyOpenSSLContext: +class PyOpenSSLContext(object): """ I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible for translating the interface of the standard library ``SSLContext`` object to calls into PyOpenSSL. """ - def __init__(self, protocol: int) -> None: + def __init__(self, protocol): self.protocol = _openssl_versions[protocol] self._ctx = OpenSSL.SSL.Context(self.protocol) self._options = 0 self.check_hostname = False - self._minimum_version: int = ssl.TLSVersion.MINIMUM_SUPPORTED - self._maximum_version: int = ssl.TLSVersion.MAXIMUM_SUPPORTED - self._verify_flags: int = ssl.VERIFY_X509_TRUSTED_FIRST @property - def options(self) -> int: + def options(self): return self._options @options.setter - def options(self, value: int) -> None: + def options(self, value): self._options = value - self._set_ctx_options() + self._ctx.set_options(value) @property - def verify_flags(self) -> int: - return self._verify_flags - - @verify_flags.setter - def verify_flags(self, value: int) -> None: - self._verify_flags = value - self._ctx.get_cert_store().set_flags(self._verify_flags) - - @property - def verify_mode(self) -> int: + def verify_mode(self): return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] @verify_mode.setter - def verify_mode(self, value: ssl.VerifyMode) -> None: + def verify_mode(self, value): self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) - def set_default_verify_paths(self) -> None: + def set_default_verify_paths(self): self._ctx.set_default_verify_paths() - def set_ciphers(self, ciphers: bytes | str) -> None: - if isinstance(ciphers, str): + def set_ciphers(self, ciphers): + if isinstance(ciphers, six.text_type): ciphers = ciphers.encode("utf-8") self._ctx.set_cipher_list(ciphers) - def load_verify_locations( - self, - cafile: str | None = None, - capath: str | None = None, - cadata: bytes | None = None, - ) -> None: + def load_verify_locations(self, cafile=None, capath=None, cadata=None): if cafile is not None: - cafile = cafile.encode("utf-8") # type: ignore[assignment] + cafile = cafile.encode("utf-8") if capath is not None: - capath = capath.encode("utf-8") # type: ignore[assignment] + capath = capath.encode("utf-8") try: self._ctx.load_verify_locations(cafile, capath) if cadata is not None: self._ctx.load_verify_locations(BytesIO(cadata)) except OpenSSL.SSL.Error as e: - raise ssl.SSLError(f"unable to load trusted certificates: {e!r}") from e + raise ssl.SSLError("unable to load trusted certificates: %r" % e) - def load_cert_chain( - self, - certfile: str, - keyfile: str | None = None, - password: str | None = None, - ) -> None: - try: - self._ctx.use_certificate_chain_file(certfile) - if password is not None: - if not isinstance(password, bytes): - password = password.encode("utf-8") # type: ignore[assignment] - self._ctx.set_passwd_cb(lambda *_: password) - self._ctx.use_privatekey_file(keyfile or certfile) - except OpenSSL.SSL.Error as e: - raise ssl.SSLError(f"Unable to load certificate chain: {e!r}") from e + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._ctx.use_certificate_chain_file(certfile) + if password is not None: + if not isinstance(password, six.binary_type): + password = password.encode("utf-8") + self._ctx.set_passwd_cb(lambda *_: password) + self._ctx.use_privatekey_file(keyfile or certfile) - def set_alpn_protocols(self, protocols: list[bytes | str]) -> None: - protocols = [util.util.to_bytes(p, "ascii") for p in protocols] - return self._ctx.set_alpn_protos(protocols) # type: ignore[no-any-return] + def set_alpn_protocols(self, protocols): + protocols = [six.ensure_binary(p) for p in protocols] + return self._ctx.set_alpn_protos(protocols) def wrap_socket( self, - sock: socket_cls, - server_side: bool = False, - do_handshake_on_connect: bool = True, - suppress_ragged_eofs: bool = True, - server_hostname: bytes | str | None = None, - ) -> WrappedSocket: + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): cnx = OpenSSL.SSL.Connection(self._ctx, sock) - # If server_hostname is an IP, don't use it for SNI, per RFC6066 Section 3 - if server_hostname and not util.ssl_.is_ipaddress(server_hostname): - if isinstance(server_hostname, str): - server_hostname = server_hostname.encode("utf-8") + if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 + server_hostname = server_hostname.encode("utf-8") + + if server_hostname is not None: cnx.set_tlsext_host_name(server_hostname) cnx.set_connect_state() @@ -517,47 +503,16 @@ class PyOpenSSLContext: while True: try: cnx.do_handshake() - except OpenSSL.SSL.WantReadError as e: + except OpenSSL.SSL.WantReadError: if not util.wait_for_read(sock, sock.gettimeout()): - raise TimeoutError("select timed out") from e + raise timeout("select timed out") continue except OpenSSL.SSL.Error as e: - raise ssl.SSLError(f"bad handshake: {e!r}") from e + raise ssl.SSLError("bad handshake: %r" % e) break return WrappedSocket(cnx, sock) - def _set_ctx_options(self) -> None: - self._ctx.set_options( - self._options - | _openssl_to_ssl_minimum_version[self._minimum_version] - | _openssl_to_ssl_maximum_version[self._maximum_version] - ) - @property - def minimum_version(self) -> int: - return self._minimum_version - - @minimum_version.setter - def minimum_version(self, minimum_version: int) -> None: - self._minimum_version = minimum_version - self._set_ctx_options() - - @property - def maximum_version(self) -> int: - return self._maximum_version - - @maximum_version.setter - def maximum_version(self, maximum_version: int) -> None: - self._maximum_version = maximum_version - self._set_ctx_options() - - -def _verify_callback( - cnx: OpenSSL.SSL.Connection, - x509: X509, - err_no: int, - err_depth: int, - return_code: int, -) -> bool: +def _verify_callback(cnx, x509, err_no, err_depth, return_code): return err_no == 0 diff --git a/venv/lib/python3.12/site-packages/urllib3/contrib/socks.py b/venv/lib/python3.12/site-packages/urllib3/contrib/socks.py index d37da8f..c326e80 100644 --- a/venv/lib/python3.12/site-packages/urllib3/contrib/socks.py +++ b/venv/lib/python3.12/site-packages/urllib3/contrib/socks.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ This module contains provisional support for SOCKS proxies from within urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and @@ -37,11 +38,10 @@ with the proxy: proxy_url="socks5h://:@proxy-host" """ - -from __future__ import annotations +from __future__ import absolute_import try: - import socks # type: ignore[import-untyped] + import socks except ImportError: import warnings @@ -51,13 +51,13 @@ except ImportError: ( "SOCKS support in urllib3 requires the installation of optional " "dependencies: specifically, PySocks. For more information, see " - "https://urllib3.readthedocs.io/en/latest/advanced-usage.html#socks-proxies" + "https://urllib3.readthedocs.io/en/1.26.x/contrib.html#socks-proxies" ), DependencyWarning, ) raise -import typing +from socket import error as SocketError from socket import timeout as SocketTimeout from ..connection import HTTPConnection, HTTPSConnection @@ -69,16 +69,7 @@ from ..util.url import parse_url try: import ssl except ImportError: - ssl = None # type: ignore[assignment] - - -class _TYPE_SOCKS_OPTIONS(typing.TypedDict): - socks_version: int - proxy_host: str | None - proxy_port: str | None - username: str | None - password: str | None - rdns: bool + ssl = None class SOCKSConnection(HTTPConnection): @@ -86,20 +77,15 @@ class SOCKSConnection(HTTPConnection): A plain-text HTTP connection that connects via a SOCKS proxy. """ - def __init__( - self, - _socks_options: _TYPE_SOCKS_OPTIONS, - *args: typing.Any, - **kwargs: typing.Any, - ) -> None: - self._socks_options = _socks_options - super().__init__(*args, **kwargs) + def __init__(self, *args, **kwargs): + self._socks_options = kwargs.pop("_socks_options") + super(SOCKSConnection, self).__init__(*args, **kwargs) - def _new_conn(self) -> socks.socksocket: + def _new_conn(self): """ Establish a new connection via the SOCKS proxy. """ - extra_kw: dict[str, typing.Any] = {} + extra_kw = {} if self.source_address: extra_kw["source_address"] = self.source_address @@ -116,14 +102,15 @@ class SOCKSConnection(HTTPConnection): proxy_password=self._socks_options["password"], proxy_rdns=self._socks_options["rdns"], timeout=self.timeout, - **extra_kw, + **extra_kw ) - except SocketTimeout as e: + except SocketTimeout: raise ConnectTimeoutError( self, - f"Connection to {self.host} timed out. (connect timeout={self.timeout})", - ) from e + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) except socks.ProxyError as e: # This is fragile as hell, but it seems to be the only way to raise @@ -133,23 +120,22 @@ class SOCKSConnection(HTTPConnection): if isinstance(error, SocketTimeout): raise ConnectTimeoutError( self, - f"Connection to {self.host} timed out. (connect timeout={self.timeout})", - ) from e - else: - # Adding `from e` messes with coverage somehow, so it's omitted. - # See #2386. - raise NewConnectionError( - self, f"Failed to establish a new connection: {error}" + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), ) - else: # Defensive: see https://github.com/urllib3/urllib3/pull/3728#pullrequestreview-3816302703 + else: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % error + ) + else: raise NewConnectionError( - self, f"Failed to establish a new connection: {e}" - ) from e + self, "Failed to establish a new connection: %s" % e + ) - except OSError as e: # Defensive: PySocks should catch all these. + except SocketError as e: # Defensive: PySocks should catch all these. raise NewConnectionError( - self, f"Failed to establish a new connection: {e}" - ) from e + self, "Failed to establish a new connection: %s" % e + ) return conn @@ -183,12 +169,12 @@ class SOCKSProxyManager(PoolManager): def __init__( self, - proxy_url: str, - username: str | None = None, - password: str | None = None, - num_pools: int = 10, - headers: typing.Mapping[str, str] | None = None, - **connection_pool_kw: typing.Any, + proxy_url, + username=None, + password=None, + num_pools=10, + headers=None, + **connection_pool_kw ): parsed = parse_url(proxy_url) @@ -209,7 +195,7 @@ class SOCKSProxyManager(PoolManager): socks_version = socks.PROXY_TYPE_SOCKS4 rdns = True else: - raise ValueError(f"Unable to determine SOCKS version from {proxy_url}") + raise ValueError("Unable to determine SOCKS version from %s" % proxy_url) self.proxy_url = proxy_url @@ -223,6 +209,8 @@ class SOCKSProxyManager(PoolManager): } connection_pool_kw["_socks_options"] = socks_options - super().__init__(num_pools, headers, **connection_pool_kw) + super(SOCKSProxyManager, self).__init__( + num_pools, headers, **connection_pool_kw + ) self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/venv/lib/python3.12/site-packages/urllib3/exceptions.py b/venv/lib/python3.12/site-packages/urllib3/exceptions.py index 3d7e9b9..cba6f3f 100644 --- a/venv/lib/python3.12/site-packages/urllib3/exceptions.py +++ b/venv/lib/python3.12/site-packages/urllib3/exceptions.py @@ -1,16 +1,6 @@ -from __future__ import annotations +from __future__ import absolute_import -import socket -import typing -import warnings -from email.errors import MessageDefect -from http.client import IncompleteRead as httplib_IncompleteRead - -if typing.TYPE_CHECKING: - from .connection import HTTPConnection - from .connectionpool import ConnectionPool - from .response import HTTPResponse - from .util.retry import Retry +from .packages.six.moves.http_client import IncompleteRead as httplib_IncompleteRead # Base Exceptions @@ -18,61 +8,64 @@ if typing.TYPE_CHECKING: class HTTPError(Exception): """Base exception used by this module.""" + pass + class HTTPWarning(Warning): """Base warning used by this module.""" - -_TYPE_REDUCE_RESULT = tuple[typing.Callable[..., object], tuple[object, ...]] + pass class PoolError(HTTPError): """Base exception for errors caused within a pool.""" - def __init__(self, pool: ConnectionPool, message: str) -> None: + def __init__(self, pool, message): self.pool = pool - self._message = message - super().__init__(f"{pool}: {message}") + HTTPError.__init__(self, "%s: %s" % (pool, message)) - def __reduce__(self) -> _TYPE_REDUCE_RESULT: + def __reduce__(self): # For pickling purposes. - return self.__class__, (None, self._message) + return self.__class__, (None, None) class RequestError(PoolError): """Base exception for PoolErrors that have associated URLs.""" - def __init__(self, pool: ConnectionPool, url: str | None, message: str) -> None: + def __init__(self, pool, url, message): self.url = url - super().__init__(pool, message) + PoolError.__init__(self, pool, message) - def __reduce__(self) -> _TYPE_REDUCE_RESULT: + def __reduce__(self): # For pickling purposes. - return self.__class__, (None, self.url, self._message) + return self.__class__, (None, self.url, None) class SSLError(HTTPError): """Raised when SSL certificate fails in an HTTPS connection.""" + pass + class ProxyError(HTTPError): """Raised when the connection to a proxy fails.""" - # The original error is also available as __cause__. - original_error: Exception - - def __init__(self, message: str, error: Exception) -> None: - super().__init__(message, error) + def __init__(self, message, error, *args): + super(ProxyError, self).__init__(message, error, *args) self.original_error = error class DecodeError(HTTPError): """Raised when automatic decoding based on Content-Type fails.""" + pass + class ProtocolError(HTTPError): """Raised when something unexpected happens mid-request/response.""" + pass + #: Renamed to ProtocolError but aliased for backwards compatibility. ConnectionError = ProtocolError @@ -86,40 +79,33 @@ class MaxRetryError(RequestError): :param pool: The connection pool :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` - :param str url: The requested Url - :param reason: The underlying error - :type reason: :class:`Exception` + :param string url: The requested Url + :param exceptions.Exception reason: The underlying error """ - def __init__( - self, pool: ConnectionPool, url: str | None, reason: Exception | None = None - ) -> None: + def __init__(self, pool, url, reason=None): self.reason = reason - message = f"Max retries exceeded with url: {url} (Caused by {reason!r})" + message = "Max retries exceeded with url: %s (Caused by %r)" % (url, reason) - super().__init__(pool, url, message) - - def __reduce__(self) -> _TYPE_REDUCE_RESULT: - # For pickling purposes. - return self.__class__, (None, self.url, self.reason) + RequestError.__init__(self, pool, url, message) class HostChangedError(RequestError): """Raised when an existing pool gets a request for a foreign host.""" - def __init__( - self, pool: ConnectionPool, url: str, retries: Retry | int = 3 - ) -> None: - message = f"Tried to open a foreign host with url: {url}" - super().__init__(pool, url, message) + def __init__(self, pool, url, retries=3): + message = "Tried to open a foreign host with url: %s" % url + RequestError.__init__(self, pool, url, message) self.retries = retries class TimeoutStateError(HTTPError): """Raised when passing an invalid state to a timeout""" + pass + class TimeoutError(HTTPError): """Raised when a socket timeout error occurs. @@ -128,77 +114,53 @@ class TimeoutError(HTTPError): ` and :exc:`ConnectTimeoutErrors `. """ + pass + class ReadTimeoutError(TimeoutError, RequestError): """Raised when a socket timeout occurs while receiving data from a server""" + pass + # This timeout error does not have a URL attached and needs to inherit from the # base HTTPError class ConnectTimeoutError(TimeoutError): """Raised when a socket timeout occurs while connecting to a server""" + pass -class NewConnectionError(ConnectTimeoutError, HTTPError): + +class NewConnectionError(ConnectTimeoutError, PoolError): """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" - def __init__(self, conn: HTTPConnection, message: str) -> None: - self.conn = conn - self._message = message - super().__init__(f"{conn}: {message}") - - def __reduce__(self) -> _TYPE_REDUCE_RESULT: - # For pickling purposes. - return self.__class__, (None, self._message) - - @property - def pool(self) -> HTTPConnection: - warnings.warn( - "The 'pool' property is deprecated and will be removed " - "in urllib3 v3.0. Use 'conn' instead.", - FutureWarning, - stacklevel=2, - ) - - return self.conn - - -class NameResolutionError(NewConnectionError): - """Raised when host name resolution fails.""" - - def __init__(self, host: str, conn: HTTPConnection, reason: socket.gaierror): - message = f"Failed to resolve '{host}' ({reason})" - self._host = host - self._reason = reason - super().__init__(conn, message) - - def __reduce__(self) -> _TYPE_REDUCE_RESULT: - # For pickling purposes. - return self.__class__, (self._host, None, self._reason) + pass class EmptyPoolError(PoolError): """Raised when a pool runs out of connections and no more are allowed.""" - -class FullPoolError(PoolError): - """Raised when we try to add a connection to a full pool in blocking mode.""" + pass class ClosedPoolError(PoolError): """Raised when a request enters a pool after the pool has been closed.""" + pass + class LocationValueError(ValueError, HTTPError): """Raised when there is something wrong with a given URL input.""" + pass + class LocationParseError(LocationValueError): """Raised when get_host or similar fails to parse the URL input.""" - def __init__(self, location: str) -> None: - message = f"Failed to parse: {location}" - super().__init__(message) + def __init__(self, location): + message = "Failed to parse: %s" % location + HTTPError.__init__(self, message) self.location = location @@ -206,9 +168,9 @@ class LocationParseError(LocationValueError): class URLSchemeUnknown(LocationValueError): """Raised when a URL input has an unsupported scheme.""" - def __init__(self, scheme: str): - message = f"Not supported URL scheme {scheme}" - super().__init__(message) + def __init__(self, scheme): + message = "Not supported URL scheme %s" % scheme + super(URLSchemeUnknown, self).__init__(message) self.scheme = scheme @@ -223,22 +185,38 @@ class ResponseError(HTTPError): class SecurityWarning(HTTPWarning): """Warned when performing security reducing actions""" + pass + + +class SubjectAltNameWarning(SecurityWarning): + """Warned when connecting to a host with a certificate missing a SAN.""" + + pass + class InsecureRequestWarning(SecurityWarning): """Warned when making an unverified HTTPS request.""" - -class NotOpenSSLWarning(SecurityWarning): - """Warned when using unsupported SSL library""" + pass class SystemTimeWarning(SecurityWarning): """Warned when system time is suspected to be wrong""" + pass + class InsecurePlatformWarning(SecurityWarning): """Warned when certain TLS/SSL configuration is not available on a platform.""" + pass + + +class SNIMissingWarning(HTTPWarning): + """Warned when making a HTTPS request without SNI available.""" + + pass + class DependencyWarning(HTTPWarning): """ @@ -246,10 +224,14 @@ class DependencyWarning(HTTPWarning): dependencies. """ + pass + class ResponseNotChunked(ProtocolError, ValueError): """Response needs to be chunked in order to read it as chunks.""" + pass + class BodyNotHttplibCompatible(HTTPError): """ @@ -257,6 +239,8 @@ class BodyNotHttplibCompatible(HTTPError): (have an fp attribute which returns raw chunks) for read_chunked(). """ + pass + class IncompleteRead(HTTPError, httplib_IncompleteRead): """ @@ -266,14 +250,10 @@ class IncompleteRead(HTTPError, httplib_IncompleteRead): for ``partial`` to avoid creating large objects on streamed reads. """ - partial: int # type: ignore[assignment] - expected: int + def __init__(self, partial, expected): + super(IncompleteRead, self).__init__(partial, expected) - def __init__(self, partial: int, expected: int) -> None: - self.partial = partial - self.expected = expected - - def __repr__(self) -> str: + def __repr__(self): return "IncompleteRead(%i bytes read, %i more expected)" % ( self.partial, self.expected, @@ -283,13 +263,14 @@ class IncompleteRead(HTTPError, httplib_IncompleteRead): class InvalidChunkLength(HTTPError, httplib_IncompleteRead): """Invalid chunk length in a chunked response.""" - def __init__(self, response: HTTPResponse, length: bytes) -> None: - self.partial: int = response.tell() # type: ignore[assignment] - self.expected: int | None = response.length_remaining + def __init__(self, response, length): + super(InvalidChunkLength, self).__init__( + response.tell(), response.length_remaining + ) self.response = response self.length = length - def __repr__(self) -> str: + def __repr__(self): return "InvalidChunkLength(got length %r, %i bytes read)" % ( self.length, self.partial, @@ -299,13 +280,15 @@ class InvalidChunkLength(HTTPError, httplib_IncompleteRead): class InvalidHeader(HTTPError): """The header provided was somehow invalid.""" + pass + class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): """ProxyManager does not support the supplied scheme""" # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. - def __init__(self, scheme: str | None) -> None: + def __init__(self, scheme): # 'localhost' is here because our URL parser parses # localhost:8080 -> scheme=localhost, remove if we fix this. if scheme == "localhost": @@ -313,23 +296,28 @@ class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): if scheme is None: message = "Proxy URL had no scheme, should start with http:// or https://" else: - message = f"Proxy URL had unsupported scheme {scheme}, should use http:// or https://" - super().__init__(message) + message = ( + "Proxy URL had unsupported scheme %s, should use http:// or https://" + % scheme + ) + super(ProxySchemeUnknown, self).__init__(message) class ProxySchemeUnsupported(ValueError): """Fetching HTTPS resources through HTTPS proxies is unsupported""" + pass + class HeaderParsingError(HTTPError): """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" - def __init__( - self, defects: list[MessageDefect], unparsed_data: bytes | str | None - ) -> None: - message = f"{defects or 'Unknown'}, unparsed data: {unparsed_data!r}" - super().__init__(message) + def __init__(self, defects, unparsed_data): + message = "%s, unparsed data: %r" % (defects or "Unknown", unparsed_data) + super(HeaderParsingError, self).__init__(message) class UnrewindableBodyError(HTTPError): """urllib3 encountered an error when trying to rewind a body""" + + pass diff --git a/venv/lib/python3.12/site-packages/urllib3/fields.py b/venv/lib/python3.12/site-packages/urllib3/fields.py index fe68e17..9d630f4 100644 --- a/venv/lib/python3.12/site-packages/urllib3/fields.py +++ b/venv/lib/python3.12/site-packages/urllib3/fields.py @@ -1,20 +1,13 @@ -from __future__ import annotations +from __future__ import absolute_import import email.utils import mimetypes -import typing +import re -_TYPE_FIELD_VALUE = typing.Union[str, bytes] -_TYPE_FIELD_VALUE_TUPLE = typing.Union[ - _TYPE_FIELD_VALUE, - tuple[str, _TYPE_FIELD_VALUE], - tuple[str, _TYPE_FIELD_VALUE, str], -] +from .packages import six -def guess_content_type( - filename: str | None, default: str = "application/octet-stream" -) -> str: +def guess_content_type(filename, default="application/octet-stream"): """ Guess the "Content-Type" of a file. @@ -28,7 +21,7 @@ def guess_content_type( return default -def format_header_param_rfc2231(name: str, value: _TYPE_FIELD_VALUE) -> str: +def format_header_param_rfc2231(name, value): """ Helper function to format and quote a single header parameter using the strategy defined in RFC 2231. @@ -41,28 +34,14 @@ def format_header_param_rfc2231(name: str, value: _TYPE_FIELD_VALUE) -> str: The name of the parameter, a string expected to be ASCII only. :param value: The value of the parameter, provided as ``bytes`` or `str``. - :returns: + :ret: An RFC-2231-formatted unicode string. - - .. deprecated:: 2.0.0 - Will be removed in urllib3 v3.0. This is not valid for - ``multipart/form-data`` header parameters. """ - import warnings - - warnings.warn( - "'format_header_param_rfc2231' is insecure, deprecated and will be " - "removed in urllib3 v3.0. This is not valid for " - "multipart/form-data header parameters.", - FutureWarning, - stacklevel=2, - ) - - if isinstance(value, bytes): + if isinstance(value, six.binary_type): value = value.decode("utf-8") if not any(ch in value for ch in '"\\\r\n'): - result = f'{name}="{value}"' + result = u'%s="%s"' % (name, value) try: result.encode("ascii") except (UnicodeEncodeError, UnicodeDecodeError): @@ -70,87 +49,81 @@ def format_header_param_rfc2231(name: str, value: _TYPE_FIELD_VALUE) -> str: else: return result + if six.PY2: # Python 2: + value = value.encode("utf-8") + + # encode_rfc2231 accepts an encoded string and returns an ascii-encoded + # string in Python 2 but accepts and returns unicode strings in Python 3 value = email.utils.encode_rfc2231(value, "utf-8") - value = f"{name}*={value}" + value = "%s*=%s" % (name, value) + + if six.PY2: # Python 2: + value = value.decode("utf-8") return value -def format_multipart_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: +_HTML5_REPLACEMENTS = { + u"\u0022": u"%22", + # Replace "\" with "\\". + u"\u005C": u"\u005C\u005C", +} + +# All control characters from 0x00 to 0x1F *except* 0x1B. +_HTML5_REPLACEMENTS.update( + { + six.unichr(cc): u"%{:02X}".format(cc) + for cc in range(0x00, 0x1F + 1) + if cc not in (0x1B,) + } +) + + +def _replace_multiple(value, needles_and_replacements): + def replacer(match): + return needles_and_replacements[match.group(0)] + + pattern = re.compile( + r"|".join([re.escape(needle) for needle in needles_and_replacements.keys()]) + ) + + result = pattern.sub(replacer, value) + + return result + + +def format_header_param_html5(name, value): """ - Format and quote a single multipart header parameter. + Helper function to format and quote a single header parameter using the + HTML5 strategy. - This follows the `WHATWG HTML Standard`_ as of 2021/06/10, matching - the behavior of current browser and curl versions. Values are - assumed to be UTF-8. The ``\\n``, ``\\r``, and ``"`` characters are - percent encoded. + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows the `HTML5 Working Draft + Section 4.10.22.7`_ and matches the behavior of curl and modern browsers. - .. _WHATWG HTML Standard: - https://html.spec.whatwg.org/multipage/ - form-control-infrastructure.html#multipart-form-data + .. _HTML5 Working Draft Section 4.10.22.7: + https://w3c.github.io/html/sec-forms.html#multipart-form-data :param name: - The name of the parameter, an ASCII-only ``str``. + The name of the parameter, a string expected to be ASCII only. :param value: - The value of the parameter, a ``str`` or UTF-8 encoded - ``bytes``. - :returns: - A string ``name="value"`` with the escaped value. - - .. versionchanged:: 2.0.0 - Matches the WHATWG HTML Standard as of 2021/06/10. Control - characters are no longer percent encoded. - - .. versionchanged:: 2.0.0 - Renamed from ``format_header_param_html5`` and - ``format_header_param``. The old names will be removed in - urllib3 v3.0. + The value of the parameter, provided as ``bytes`` or `str``. + :ret: + A unicode string, stripped of troublesome characters. """ - if isinstance(value, bytes): + if isinstance(value, six.binary_type): value = value.decode("utf-8") - # percent encode \n \r " - value = value.translate({10: "%0A", 13: "%0D", 34: "%22"}) - return f'{name}="{value}"' + value = _replace_multiple(value, _HTML5_REPLACEMENTS) + + return u'%s="%s"' % (name, value) -def format_header_param_html5(name: str, value: _TYPE_FIELD_VALUE) -> str: - """ - .. deprecated:: 2.0.0 - Renamed to :func:`format_multipart_header_param`. Will be - removed in urllib3 v3.0. - """ - import warnings - - warnings.warn( - "'format_header_param_html5' has been renamed to " - "'format_multipart_header_param'. The old name will be " - "removed in urllib3 v3.0.", - FutureWarning, - stacklevel=2, - ) - return format_multipart_header_param(name, value) +# For backwards-compatibility. +format_header_param = format_header_param_html5 -def format_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: - """ - .. deprecated:: 2.0.0 - Renamed to :func:`format_multipart_header_param`. Will be - removed in urllib3 v3.0. - """ - import warnings - - warnings.warn( - "'format_header_param' has been renamed to " - "'format_multipart_header_param'. The old name will be " - "removed in urllib3 v3.0.", - FutureWarning, - stacklevel=2, - ) - return format_multipart_header_param(name, value) - - -class RequestField: +class RequestField(object): """ A data container for request body parameters. @@ -162,47 +135,29 @@ class RequestField: An optional filename of the request field. Must be unicode. :param headers: An optional dict-like object of headers to initially use for the field. - - .. versionchanged:: 2.0.0 - The ``header_formatter`` parameter is deprecated and will - be removed in urllib3 v3.0. + :param header_formatter: + An optional callable that is used to encode and format the headers. By + default, this is :func:`format_header_param_html5`. """ def __init__( self, - name: str, - data: _TYPE_FIELD_VALUE, - filename: str | None = None, - headers: typing.Mapping[str, str] | None = None, - header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, + name, + data, + filename=None, + headers=None, + header_formatter=format_header_param_html5, ): self._name = name self._filename = filename self.data = data - self.headers: dict[str, str | None] = {} + self.headers = {} if headers: self.headers = dict(headers) - - if header_formatter is not None: - import warnings - - warnings.warn( - "The 'header_formatter' parameter is deprecated and " - "will be removed in urllib3 v3.0.", - FutureWarning, - stacklevel=2, - ) - self.header_formatter = header_formatter - else: - self.header_formatter = format_multipart_header_param + self.header_formatter = header_formatter @classmethod - def from_tuples( - cls, - fieldname: str, - value: _TYPE_FIELD_VALUE_TUPLE, - header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, - ) -> RequestField: + def from_tuples(cls, fieldname, value, header_formatter=format_header_param_html5): """ A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. @@ -219,10 +174,6 @@ class RequestField: Field names and filenames must be unicode. """ - filename: str | None - content_type: str | None - data: _TYPE_FIELD_VALUE - if isinstance(value, tuple): if len(value) == 3: filename, data, content_type = value @@ -241,29 +192,20 @@ class RequestField: return request_param - def _render_part(self, name: str, value: _TYPE_FIELD_VALUE) -> str: + def _render_part(self, name, value): """ - Override this method to change how each multipart header - parameter is formatted. By default, this calls - :func:`format_multipart_header_param`. + Overridable helper function to format a single header parameter. By + default, this calls ``self.header_formatter``. :param name: - The name of the parameter, an ASCII-only ``str``. + The name of the parameter, a string expected to be ASCII only. :param value: - The value of the parameter, a ``str`` or UTF-8 encoded - ``bytes``. - - :meta public: + The value of the parameter, provided as a unicode string. """ + return self.header_formatter(name, value) - def _render_parts( - self, - header_parts: ( - dict[str, _TYPE_FIELD_VALUE | None] - | typing.Sequence[tuple[str, _TYPE_FIELD_VALUE | None]] - ), - ) -> str: + def _render_parts(self, header_parts): """ Helper function to format and quote a single header. @@ -274,21 +216,18 @@ class RequestField: A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format as `k1="v1"; k2="v2"; ...`. """ - iterable: typing.Iterable[tuple[str, _TYPE_FIELD_VALUE | None]] - parts = [] + iterable = header_parts if isinstance(header_parts, dict): iterable = header_parts.items() - else: - iterable = header_parts for name, value in iterable: if value is not None: parts.append(self._render_part(name, value)) - return "; ".join(parts) + return u"; ".join(parts) - def render_headers(self) -> str: + def render_headers(self): """ Renders the headers for this request field. """ @@ -297,45 +236,39 @@ class RequestField: sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] for sort_key in sort_keys: if self.headers.get(sort_key, False): - lines.append(f"{sort_key}: {self.headers[sort_key]}") + lines.append(u"%s: %s" % (sort_key, self.headers[sort_key])) for header_name, header_value in self.headers.items(): if header_name not in sort_keys: if header_value: - lines.append(f"{header_name}: {header_value}") + lines.append(u"%s: %s" % (header_name, header_value)) - lines.append("\r\n") - return "\r\n".join(lines) + lines.append(u"\r\n") + return u"\r\n".join(lines) def make_multipart( - self, - content_disposition: str | None = None, - content_type: str | None = None, - content_location: str | None = None, - ) -> None: + self, content_disposition=None, content_type=None, content_location=None + ): """ Makes this request field into a multipart request field. This method overrides "Content-Disposition", "Content-Type" and "Content-Location" headers to the request parameter. - :param content_disposition: - The 'Content-Disposition' of the request body. Defaults to 'form-data' :param content_type: The 'Content-Type' of the request body. :param content_location: The 'Content-Location' of the request body. """ - content_disposition = (content_disposition or "form-data") + "; ".join( + self.headers["Content-Disposition"] = content_disposition or u"form-data" + self.headers["Content-Disposition"] += u"; ".join( [ - "", + u"", self._render_parts( - (("name", self._name), ("filename", self._filename)) + ((u"name", self._name), (u"filename", self._filename)) ), ] ) - - self.headers["Content-Disposition"] = content_disposition self.headers["Content-Type"] = content_type self.headers["Content-Location"] = content_location diff --git a/venv/lib/python3.12/site-packages/urllib3/filepost.py b/venv/lib/python3.12/site-packages/urllib3/filepost.py index 14f70b0..36c9252 100644 --- a/venv/lib/python3.12/site-packages/urllib3/filepost.py +++ b/venv/lib/python3.12/site-packages/urllib3/filepost.py @@ -1,32 +1,28 @@ -from __future__ import annotations +from __future__ import absolute_import import binascii import codecs import os -import typing from io import BytesIO -from .fields import _TYPE_FIELD_VALUE_TUPLE, RequestField +from .fields import RequestField +from .packages import six +from .packages.six import b writer = codecs.lookup("utf-8")[3] -_TYPE_FIELDS_SEQUENCE = typing.Sequence[ - typing.Union[tuple[str, _TYPE_FIELD_VALUE_TUPLE], RequestField] -] -_TYPE_FIELDS = typing.Union[ - _TYPE_FIELDS_SEQUENCE, - typing.Mapping[str, _TYPE_FIELD_VALUE_TUPLE], -] - -def choose_boundary() -> str: +def choose_boundary(): """ Our embarrassingly-simple replacement for mimetools.choose_boundary. """ - return binascii.hexlify(os.urandom(16)).decode() + boundary = binascii.hexlify(os.urandom(16)) + if not six.PY2: + boundary = boundary.decode("ascii") + return boundary -def iter_field_objects(fields: _TYPE_FIELDS) -> typing.Iterable[RequestField]: +def iter_field_objects(fields): """ Iterate over fields. @@ -34,29 +30,42 @@ def iter_field_objects(fields: _TYPE_FIELDS) -> typing.Iterable[RequestField]: :class:`~urllib3.fields.RequestField`. """ - iterable: typing.Iterable[RequestField | tuple[str, _TYPE_FIELD_VALUE_TUPLE]] - - if isinstance(fields, typing.Mapping): - iterable = fields.items() + if isinstance(fields, dict): + i = six.iteritems(fields) else: - iterable = fields + i = iter(fields) - for field in iterable: + for field in i: if isinstance(field, RequestField): yield field else: yield RequestField.from_tuples(*field) -def encode_multipart_formdata( - fields: _TYPE_FIELDS, boundary: str | None = None -) -> tuple[bytes, str]: +def iter_fields(fields): + """ + .. deprecated:: 1.6 + + Iterate over fields. + + The addition of :class:`~urllib3.fields.RequestField` makes this function + obsolete. Instead, use :func:`iter_field_objects`, which returns + :class:`~urllib3.fields.RequestField` objects. + + Supports list of (k, v) tuples and dicts. + """ + if isinstance(fields, dict): + return ((k, v) for k, v in six.iteritems(fields)) + + return ((k, v) for k, v in fields) + + +def encode_multipart_formdata(fields, boundary=None): """ Encode a dictionary of ``fields`` using the multipart/form-data MIME format. :param fields: Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). - Values are processed by :func:`urllib3.fields.RequestField.from_tuples`. :param boundary: If not specified, then a random boundary will be generated using @@ -67,7 +76,7 @@ def encode_multipart_formdata( boundary = choose_boundary() for field in iter_field_objects(fields): - body.write(f"--{boundary}\r\n".encode("latin-1")) + body.write(b("--%s\r\n" % (boundary))) writer(body).write(field.render_headers()) data = field.data @@ -75,15 +84,15 @@ def encode_multipart_formdata( if isinstance(data, int): data = str(data) # Backwards compatibility - if isinstance(data, str): + if isinstance(data, six.text_type): writer(body).write(data) else: body.write(data) body.write(b"\r\n") - body.write(f"--{boundary}--\r\n".encode("latin-1")) + body.write(b("--%s--\r\n" % (boundary))) - content_type = f"multipart/form-data; boundary={boundary}" + content_type = str("multipart/form-data; boundary=%s" % boundary) return body.getvalue(), content_type diff --git a/venv/lib/python3.12/site-packages/urllib3/http2/__init__.py b/venv/lib/python3.12/site-packages/urllib3/http2/__init__.py deleted file mode 100644 index 133e1d8..0000000 --- a/venv/lib/python3.12/site-packages/urllib3/http2/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import annotations - -from importlib.metadata import version - -__all__ = [ - "inject_into_urllib3", - "extract_from_urllib3", -] - -import typing - -orig_HTTPSConnection: typing.Any = None - - -def inject_into_urllib3() -> None: - # First check if h2 version is valid - h2_version = version("h2") - if not h2_version.startswith("4."): - raise ImportError( - "urllib3 v2 supports h2 version 4.x.x, currently " - f"the 'h2' module is compiled with {h2_version!r}. " - "See: https://github.com/urllib3/urllib3/issues/3290" - ) - - # Import here to avoid circular dependencies. - from .. import connection as urllib3_connection - from .. import util as urllib3_util - from ..connectionpool import HTTPSConnectionPool - from ..util import ssl_ as urllib3_util_ssl - from .connection import HTTP2Connection - - global orig_HTTPSConnection - orig_HTTPSConnection = urllib3_connection.HTTPSConnection - - HTTPSConnectionPool.ConnectionCls = HTTP2Connection - urllib3_connection.HTTPSConnection = HTTP2Connection # type: ignore[misc] - - # TODO: Offer 'http/1.1' as well, but for testing purposes this is handy. - urllib3_util.ALPN_PROTOCOLS = ["h2"] - urllib3_util_ssl.ALPN_PROTOCOLS = ["h2"] - - -def extract_from_urllib3() -> None: - from .. import connection as urllib3_connection - from .. import util as urllib3_util - from ..connectionpool import HTTPSConnectionPool - from ..util import ssl_ as urllib3_util_ssl - - HTTPSConnectionPool.ConnectionCls = orig_HTTPSConnection - urllib3_connection.HTTPSConnection = orig_HTTPSConnection # type: ignore[misc] - - urllib3_util.ALPN_PROTOCOLS = ["http/1.1"] - urllib3_util_ssl.ALPN_PROTOCOLS = ["http/1.1"] diff --git a/venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 45834d5f1a2d18fa7a981816deb69f3db9d6f683..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1755 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 L_?*L=;m!XCAV-=A diff --git a/venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/probe.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/probe.cpython-312.pyc deleted file mode 100644 index 175b0e8b5c1dc60ee85f2b0a638cb39779410fa3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3706 zcmb6bOKcm*b!I>OGomQUk|o(*OO(`=9kRBZ1Sy=_)F*NJQCwS18>aSx-d$Ogsa<+@ zX@>&U%7zat;J^xs*6vLxS|Emj7CjWm)h^IWfkK2-kl4C_ft-4yBLixcL;Gfyq&`+t zbQXN`=FOWokMBEw4u=B>#vhj7oBLA`q0i}}IRqEM8@~eNKGKkeb7&r?F{U_|O@hd(5w zML*j8X#q_DxI8d4&|tBJVJQm0@KZI?aei`-ocBS1}-6`wVOq-0txP5tA+~^Dx!yY zN){czJxlbAmNl+A!goMD;stKvqreZ~A$9~y&&^o$a%oFT&YJUj(#RUQf|{H#)xx}P z*jCa~XH7GgeAk>#TJsrV{~&WiPu|dt8_8VuN-|%xXH8=?apGjs%G&y?`HXrkb5*yJ z1(JjP(d4Xc=T9c{)L4mp(eWut*2vn5vT$&(857N|Fz91R0G80x&cpXFe{y-PbFeJj z4b&#yE(`0vgSFInS@^53X9IYU+Oh&HJn&<7U57tK!+4P|p*hO7XS{|mD&Z10!z~Ja zu;qSsF9`?X6iDE21xvijK_bA{41SC*Le%V~fuwy>;3uvmu_VsXKeuNz{fj}~|I*|l z8bEf-({V&bF|w&&*_flw-7}if_t&f>oCbdFzurrCKR|cwo9H9{19TG)pcBZ#H#rPc z4By>ilWw60I+dY;w^Cy=$WhGl0A zRd>8Z&*`u$dr2ETBg~qX?Ff03*pA3D*Ws_}5V!!`B*(f%4KBHZ&`RaBHEvQehNfKA z?FH$j=-$H-g=5yg0W6`q2eo&W#ag)iUizo$=g99*);hW##Fmej$LpLooUC>CRl7$j z-6NllR=ShR;#1{oe-tk4JXnnlRboRcRwZ_l~CV$=x{C6{$jJjF-Eu~F}n*Zt$e{F5^S}`$fRjR$73>Rw4KcEbg~b z9g+andq*SDAw5}H;Fo&7Dh+Q1ZLujq!;c!J`!+O}-Q}S~OHZp{H%}nsrW}NRD{Z&; zwnM)NiM}V*WH;4hZffD|IBRxKkJ60)$}2%NzCv_rW`L`$CRVYxth04KcwoL3sMErr!{a)|C624GP`@5%&_ z&a$A9SNmkb9*$8erU-y#LL|04UCxzx$bj8J9PWIM1b?*Fc4)P2@Y8s8;>s)ne~&@LX54j=1*drUykOch^&W8?13{S z^E)cA2#tT<0un2r_?m-nShOiaSh&^EtGG{~h3=QCVq+%-?ACbzOXyj%3}Sx@*9PV4 z;2V{}H>!hgRR-T$8$46(IQz@#2j5%4%cTEoZaLp=W|5izy8)-*V%f@qT&1T z;Z%@+6i$i0&v?KdiyjJtVk)-V)vcm&1rB|ctJ!<2k@yI)RV&f=x~lk6&)ngjvH!1 z?T~p%_S4s;k(t*O#R(|Nyr~s(6b~uN^+G1s*zqfhl{0NiQ3!pZ5Q%~~1u_N406av5 zwr6HYLi3jaYG&50?w~p+w0oA`-3amG1l|Z75j*Q+I3)JgVqJBf&PV#)`P3OUKl6t- zM4)f<@nQP?SsEAMy}C6mj+`PV4D^+&dRr(KJDle`HVGAYKW?m%KH9Z`I#l@%Jxz;B6rus0p2Qkq>gW IyzKt}0Gj bool: - """ - "An implementation that validates fields according to the definitions in Sections - 5.1 and 5.5 of [HTTP] only needs an additional check that field names do not - include uppercase characters." (https://httpwg.org/specs/rfc9113.html#n-field-validity) - - `http.client._is_legal_header_name` does not validate the field name according to the - HTTP 1.1 spec, so we do that here, in addition to checking for uppercase characters. - - This does not allow for the `:` character in the header name, so should not - be used to validate pseudo-headers. - """ - return bool(RE_IS_LEGAL_HEADER_NAME.match(name)) - - -def _is_illegal_header_value(value: bytes) -> bool: - """ - "A field value MUST NOT contain the zero value (ASCII NUL, 0x00), line feed - (ASCII LF, 0x0a), or carriage return (ASCII CR, 0x0d) at any position. A field - value MUST NOT start or end with an ASCII whitespace character (ASCII SP or HTAB, - 0x20 or 0x09)." (https://httpwg.org/specs/rfc9113.html#n-field-validity) - """ - return bool(RE_IS_ILLEGAL_HEADER_VALUE.search(value)) - - -class _LockedObject(typing.Generic[T]): - """ - A wrapper class that hides a specific object behind a lock. - The goal here is to provide a simple way to protect access to an object - that cannot safely be simultaneously accessed from multiple threads. The - intended use of this class is simple: take hold of it with a context - manager, which returns the protected object. - """ - - __slots__ = ( - "lock", - "_obj", - ) - - def __init__(self, obj: T): - self.lock = threading.RLock() - self._obj = obj - - def __enter__(self) -> T: - self.lock.acquire() - return self._obj - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: types.TracebackType | None, - ) -> None: - self.lock.release() - - -class HTTP2Connection(HTTPSConnection): - def __init__( - self, host: str, port: int | None = None, **kwargs: typing.Any - ) -> None: - self._h2_conn = self._new_h2_conn() - self._h2_stream: int | None = None - self._headers: list[tuple[bytes, bytes]] = [] - - if "proxy" in kwargs or "proxy_config" in kwargs: # Defensive: - raise NotImplementedError("Proxies aren't supported with HTTP/2") - - super().__init__(host, port, **kwargs) - - if self._tunnel_host is not None: - raise NotImplementedError("Tunneling isn't supported with HTTP/2") - - def _new_h2_conn(self) -> _LockedObject[h2.connection.H2Connection]: - config = h2.config.H2Configuration(client_side=True) - return _LockedObject(h2.connection.H2Connection(config=config)) - - def connect(self) -> None: - super().connect() - with self._h2_conn as conn: - conn.initiate_connection() - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - - def putrequest( # type: ignore[override] - self, - method: str, - url: str, - **kwargs: typing.Any, - ) -> None: - """putrequest - This deviates from the HTTPConnection method signature since we never need to override - sending accept-encoding headers or the host header. - """ - if "skip_host" in kwargs: - raise NotImplementedError("`skip_host` isn't supported") - if "skip_accept_encoding" in kwargs: - raise NotImplementedError("`skip_accept_encoding` isn't supported") - - self._request_url = url or "/" - self._validate_path(url) # type: ignore[attr-defined] - - if ":" in self.host: - authority = f"[{self.host}]:{self.port or 443}" - else: - authority = f"{self.host}:{self.port or 443}" - - self._headers.append((b":scheme", b"https")) - self._headers.append((b":method", method.encode())) - self._headers.append((b":authority", authority.encode())) - self._headers.append((b":path", url.encode())) - - with self._h2_conn as conn: - self._h2_stream = conn.get_next_available_stream_id() - - def putheader(self, header: str | bytes, *values: str | bytes) -> None: # type: ignore[override] - # TODO SKIPPABLE_HEADERS from urllib3 are ignored. - header = header.encode() if isinstance(header, str) else header - header = header.lower() # A lot of upstream code uses capitalized headers. - if not _is_legal_header_name(header): - raise ValueError(f"Illegal header name {str(header)}") - - for value in values: - value = value.encode() if isinstance(value, str) else value - if _is_illegal_header_value(value): - raise ValueError(f"Illegal header value {str(value)}") - self._headers.append((header, value)) - - def endheaders(self, message_body: typing.Any = None) -> None: # type: ignore[override] - if self._h2_stream is None: - raise ConnectionError("Must call `putrequest` first.") - - with self._h2_conn as conn: - conn.send_headers( - stream_id=self._h2_stream, - headers=self._headers, - end_stream=(message_body is None), - ) - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - self._headers = [] # Reset headers for the next request. - - def send(self, data: typing.Any) -> None: - """Send data to the server. - `data` can be: `str`, `bytes`, an iterable, or file-like objects - that support a .read() method. - """ - if self._h2_stream is None: - raise ConnectionError("Must call `putrequest` first.") - - with self._h2_conn as conn: - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - - if hasattr(data, "read"): # file-like objects - while True: - chunk = data.read(self.blocksize) - if not chunk: - break - if isinstance(chunk, str): - chunk = chunk.encode() - conn.send_data(self._h2_stream, chunk, end_stream=False) - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - conn.end_stream(self._h2_stream) - return - - if isinstance(data, str): # str -> bytes - data = data.encode() - - try: - if isinstance(data, bytes): - conn.send_data(self._h2_stream, data, end_stream=True) - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - else: - for chunk in data: - conn.send_data(self._h2_stream, chunk, end_stream=False) - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - conn.end_stream(self._h2_stream) - except TypeError: - raise TypeError( - "`data` should be str, bytes, iterable, or file. got %r" - % type(data) - ) - - def set_tunnel( - self, - host: str, - port: int | None = None, - headers: typing.Mapping[str, str] | None = None, - scheme: str = "http", - ) -> None: - raise NotImplementedError( - "HTTP/2 does not support setting up a tunnel through a proxy" - ) - - def getresponse( # type: ignore[override] - self, - ) -> HTTP2Response: - status = None - data = bytearray() - with self._h2_conn as conn: - end_stream = False - while not end_stream: - # TODO: Arbitrary read value. - if received_data := self.sock.recv(65535): - events = conn.receive_data(received_data) - for event in events: - if isinstance(event, h2.events.ResponseReceived): - headers = HTTPHeaderDict() - for header, value in event.headers: - if header == b":status": - status = int(value.decode()) - else: - headers.add( - header.decode("ascii"), value.decode("ascii") - ) - - elif isinstance(event, h2.events.DataReceived): - data += event.data - conn.acknowledge_received_data( - event.flow_controlled_length, event.stream_id - ) - - elif isinstance(event, h2.events.StreamEnded): - end_stream = True - - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - - assert status is not None - return HTTP2Response( - status=status, - headers=headers, - request_url=self._request_url, - data=bytes(data), - ) - - def request( # type: ignore[override] - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - *, - preload_content: bool = True, - decode_content: bool = True, - enforce_content_length: bool = True, - **kwargs: typing.Any, - ) -> None: - """Send an HTTP/2 request""" - if "chunked" in kwargs: - # TODO this is often present from upstream. - # raise NotImplementedError("`chunked` isn't supported with HTTP/2") - pass - - if self.sock is not None: - self.sock.settimeout(self.timeout) - - self.putrequest(method, url) - - headers = headers or {} - for k, v in headers.items(): - if k.lower() == "transfer-encoding" and v == "chunked": - continue - else: - self.putheader(k, v) - - if b"user-agent" not in dict(self._headers): - self.putheader(b"user-agent", _get_default_user_agent()) - - if body: - self.endheaders(message_body=body) - self.send(body) - else: - self.endheaders() - - def close(self) -> None: - with self._h2_conn as conn: - try: - conn.close_connection() - if data := conn.data_to_send(): - self.sock.sendall(data) - except Exception: - pass - - # Reset all our HTTP/2 connection state. - self._h2_conn = self._new_h2_conn() - self._h2_stream = None - self._headers = [] - - super().close() - - -class HTTP2Response(BaseHTTPResponse): - # TODO: This is a woefully incomplete response object, but works for non-streaming. - def __init__( - self, - status: int, - headers: HTTPHeaderDict, - request_url: str, - data: bytes, - decode_content: bool = False, # TODO: support decoding - ) -> None: - super().__init__( - status=status, - headers=headers, - # Following CPython, we map HTTP versions to major * 10 + minor integers - version=20, - version_string="HTTP/2", - # No reason phrase in HTTP/2 - reason=None, - decode_content=decode_content, - request_url=request_url, - ) - self._data = data - self.length_remaining = 0 - - @property - def data(self) -> bytes: - return self._data - - def get_redirect_location(self) -> None: - return None - - def close(self) -> None: - pass diff --git a/venv/lib/python3.12/site-packages/urllib3/http2/probe.py b/venv/lib/python3.12/site-packages/urllib3/http2/probe.py deleted file mode 100644 index 9ea9007..0000000 --- a/venv/lib/python3.12/site-packages/urllib3/http2/probe.py +++ /dev/null @@ -1,87 +0,0 @@ -from __future__ import annotations - -import threading - - -class _HTTP2ProbeCache: - __slots__ = ( - "_lock", - "_cache_locks", - "_cache_values", - ) - - def __init__(self) -> None: - self._lock = threading.Lock() - self._cache_locks: dict[tuple[str, int], threading.RLock] = {} - self._cache_values: dict[tuple[str, int], bool | None] = {} - - def acquire_and_get(self, host: str, port: int) -> bool | None: - # By the end of this block we know that - # _cache_[values,locks] is available. - value = None - with self._lock: - key = (host, port) - try: - value = self._cache_values[key] - # If it's a known value we return right away. - if value is not None: - return value - except KeyError: - self._cache_locks[key] = threading.RLock() - self._cache_values[key] = None - - # If the value is unknown, we acquire the lock to signal - # to the requesting thread that the probe is in progress - # or that the current thread needs to return their findings. - key_lock = self._cache_locks[key] - key_lock.acquire() - try: - # If the by the time we get the lock the value has been - # updated we want to return the updated value. - value = self._cache_values[key] - - # In case an exception like KeyboardInterrupt is raised here. - except BaseException as e: # Defensive: - assert not isinstance(e, KeyError) # KeyError shouldn't be possible. - key_lock.release() - raise - - return value - - def set_and_release( - self, host: str, port: int, supports_http2: bool | None - ) -> None: - key = (host, port) - key_lock = self._cache_locks[key] - with key_lock: # Uses an RLock, so can be locked again from same thread. - if supports_http2 is None and self._cache_values[key] is not None: - raise ValueError( - "Cannot reset HTTP/2 support for origin after value has been set." - ) # Defensive: not expected in normal usage - - self._cache_values[key] = supports_http2 - key_lock.release() - - def _values(self) -> dict[tuple[str, int], bool | None]: - """This function is for testing purposes only. Gets the current state of the probe cache""" - with self._lock: - return {k: v for k, v in self._cache_values.items()} - - def _reset(self) -> None: - """This function is for testing purposes only. Reset the cache values""" - with self._lock: - self._cache_locks = {} - self._cache_values = {} - - -_HTTP2_PROBE_CACHE = _HTTP2ProbeCache() - -set_and_release = _HTTP2_PROBE_CACHE.set_and_release -acquire_and_get = _HTTP2_PROBE_CACHE.acquire_and_get -_values = _HTTP2_PROBE_CACHE._values -_reset = _HTTP2_PROBE_CACHE._reset - -__all__ = [ - "set_and_release", - "acquire_and_get", -] diff --git a/venv/lib/python3.12/site-packages/urllib3/poolmanager.py b/venv/lib/python3.12/site-packages/urllib3/poolmanager.py index 8f2c567..fb51bf7 100644 --- a/venv/lib/python3.12/site-packages/urllib3/poolmanager.py +++ b/venv/lib/python3.12/site-packages/urllib3/poolmanager.py @@ -1,33 +1,24 @@ -from __future__ import annotations +from __future__ import absolute_import +import collections import functools import logging -import typing -import warnings -from types import TracebackType -from urllib.parse import urljoin from ._collections import HTTPHeaderDict, RecentlyUsedContainer -from ._request_methods import RequestMethods -from .connection import ProxyConfig from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme from .exceptions import ( LocationValueError, MaxRetryError, ProxySchemeUnknown, + ProxySchemeUnsupported, URLSchemeUnknown, ) -from .response import BaseHTTPResponse -from .util.connection import _TYPE_SOCKET_OPTIONS +from .packages import six +from .packages.six.moves.urllib.parse import urljoin +from .request import RequestMethods from .util.proxy import connection_requires_http_tunnel from .util.retry import Retry -from .util.timeout import Timeout -from .util.url import Url, parse_url - -if typing.TYPE_CHECKING: - import ssl - - from typing_extensions import Self +from .util.url import parse_url __all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] @@ -39,62 +30,53 @@ SSL_KEYWORDS = ( "cert_file", "cert_reqs", "ca_certs", - "ca_cert_data", "ssl_version", - "ssl_minimum_version", - "ssl_maximum_version", "ca_cert_dir", "ssl_context", "key_password", "server_hostname", ) -# Default value for `blocksize` - a new parameter introduced to -# http.client.HTTPConnection & http.client.HTTPSConnection in Python 3.7 -_DEFAULT_BLOCKSIZE = 16384 + +# All known keyword arguments that could be provided to the pool manager, its +# pools, or the underlying connections. This is used to construct a pool key. +_key_fields = ( + "key_scheme", # str + "key_host", # str + "key_port", # int + "key_timeout", # int or float or Timeout + "key_retries", # int or Retry + "key_strict", # bool + "key_block", # bool + "key_source_address", # str + "key_key_file", # str + "key_key_password", # str + "key_cert_file", # str + "key_cert_reqs", # str + "key_ca_certs", # str + "key_ssl_version", # str + "key_ca_cert_dir", # str + "key_ssl_context", # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext + "key_maxsize", # int + "key_headers", # dict + "key__proxy", # parsed proxy url + "key__proxy_headers", # dict + "key__proxy_config", # class + "key_socket_options", # list of (level (int), optname (int), value (int or str)) tuples + "key__socks_options", # dict + "key_assert_hostname", # bool or string + "key_assert_fingerprint", # str + "key_server_hostname", # str +) + +#: The namedtuple class used to construct keys for the connection pool. +#: All custom key schemes should include the fields in this key at a minimum. +PoolKey = collections.namedtuple("PoolKey", _key_fields) + +_proxy_config_fields = ("ssl_context", "use_forwarding_for_https") +ProxyConfig = collections.namedtuple("ProxyConfig", _proxy_config_fields) -class PoolKey(typing.NamedTuple): - """ - All known keyword arguments that could be provided to the pool manager, its - pools, or the underlying connections. - - All custom key schemes should include the fields in this key at a minimum. - """ - - key_scheme: str - key_host: str - key_port: int | None - key_timeout: Timeout | float | int | None - key_retries: Retry | bool | int | None - key_block: bool | None - key_source_address: tuple[str, int] | None - key_key_file: str | None - key_key_password: str | None - key_cert_file: str | None - key_cert_reqs: str | None - key_ca_certs: str | None - key_ca_cert_data: str | bytes | None - key_ssl_version: int | str | None - key_ssl_minimum_version: ssl.TLSVersion | None - key_ssl_maximum_version: ssl.TLSVersion | None - key_ca_cert_dir: str | None - key_ssl_context: ssl.SSLContext | None - key_maxsize: int | None - key_headers: frozenset[tuple[str, str]] | None - key__proxy: Url | None - key__proxy_headers: frozenset[tuple[str, str]] | None - key__proxy_config: ProxyConfig | None - key_socket_options: _TYPE_SOCKET_OPTIONS | None - key__socks_options: frozenset[tuple[str, str]] | None - key_assert_hostname: bool | str | None - key_assert_fingerprint: str | None - key_server_hostname: str | None - key_blocksize: int | None - - -def _default_key_normalizer( - key_class: type[PoolKey], request_context: dict[str, typing.Any] -) -> PoolKey: +def _default_key_normalizer(key_class, request_context): """ Create a pool key out of a request context dictionary. @@ -140,10 +122,6 @@ def _default_key_normalizer( if field not in context: context[field] = None - # Default key_blocksize to _DEFAULT_BLOCKSIZE if missing from the context - if context.get("key_blocksize") is None: - context["key_blocksize"] = _DEFAULT_BLOCKSIZE - return key_class(**context) @@ -176,50 +154,23 @@ class PoolManager(RequestMethods): Additional parameters are used to create fresh :class:`urllib3.connectionpool.ConnectionPool` instances. - Example: + Example:: - .. code-block:: python - - import urllib3 - - http = urllib3.PoolManager(num_pools=2) - - resp1 = http.request("GET", "https://google.com/") - resp2 = http.request("GET", "https://google.com/mail") - resp3 = http.request("GET", "https://yahoo.com/") - - print(len(http.pools)) - # 2 + >>> manager = PoolManager(num_pools=2) + >>> r = manager.request('GET', 'http://google.com/') + >>> r = manager.request('GET', 'http://google.com/mail') + >>> r = manager.request('GET', 'http://yahoo.com/') + >>> len(manager.pools) + 2 """ - proxy: Url | None = None - proxy_config: ProxyConfig | None = None + proxy = None + proxy_config = None - def __init__( - self, - num_pools: int = 10, - headers: typing.Mapping[str, str] | None = None, - **connection_pool_kw: typing.Any, - ) -> None: - super().__init__(headers) - # PoolManager handles redirects itself in PoolManager.urlopen(). - # It always passes redirect=False to the underlying connection pool to - # suppress per-pool redirect handling. If the user supplied a non-Retry - # value (int/bool/etc) for retries and we let the pool normalize it - # while redirect=False, the resulting Retry object would have redirect - # handling disabled, which can interfere with PoolManager's own - # redirect logic. Normalize here so redirects remain governed solely by - # PoolManager logic. - if "retries" in connection_pool_kw: - retries = connection_pool_kw["retries"] - if not isinstance(retries, Retry): - retries = Retry.from_int(retries) - connection_pool_kw = connection_pool_kw.copy() - connection_pool_kw["retries"] = retries + def __init__(self, num_pools=10, headers=None, **connection_pool_kw): + RequestMethods.__init__(self, headers) self.connection_pool_kw = connection_pool_kw - - self.pools: RecentlyUsedContainer[PoolKey, HTTPConnectionPool] self.pools = RecentlyUsedContainer(num_pools) # Locally set the pool classes and keys so other PoolManagers can @@ -227,26 +178,15 @@ class PoolManager(RequestMethods): self.pool_classes_by_scheme = pool_classes_by_scheme self.key_fn_by_scheme = key_fn_by_scheme.copy() - def __enter__(self) -> Self: + def __enter__(self): return self - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> typing.Literal[False]: + def __exit__(self, exc_type, exc_val, exc_tb): self.clear() # Return False to re-raise any potential exceptions return False - def _new_pool( - self, - scheme: str, - host: str, - port: int, - request_context: dict[str, typing.Any] | None = None, - ) -> HTTPConnectionPool: + def _new_pool(self, scheme, host, port, request_context=None): """ Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and any additional pool keyword arguments. @@ -256,15 +196,10 @@ class PoolManager(RequestMethods): connection pools handed out by :meth:`connection_from_url` and companion methods. It is intended to be overridden for customization. """ - pool_cls: type[HTTPConnectionPool] = self.pool_classes_by_scheme[scheme] + pool_cls = self.pool_classes_by_scheme[scheme] if request_context is None: request_context = self.connection_pool_kw.copy() - # Default blocksize to _DEFAULT_BLOCKSIZE if missing or explicitly - # set to 'None' in the request_context. - if request_context.get("blocksize") is None: - request_context["blocksize"] = _DEFAULT_BLOCKSIZE - # Although the context has everything necessary to create the pool, # this function has historically only used the scheme, host, and port # in the positional args. When an API change is acceptable these can @@ -278,7 +213,7 @@ class PoolManager(RequestMethods): return pool_cls(host, port, **request_context) - def clear(self) -> None: + def clear(self): """ Empty our store of pools and direct them all to close. @@ -287,13 +222,7 @@ class PoolManager(RequestMethods): """ self.pools.clear() - def connection_from_host( - self, - host: str | None, - port: int | None = None, - scheme: str | None = "http", - pool_kwargs: dict[str, typing.Any] | None = None, - ) -> HTTPConnectionPool: + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): """ Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme. @@ -316,23 +245,13 @@ class PoolManager(RequestMethods): return self.connection_from_context(request_context) - def connection_from_context( - self, request_context: dict[str, typing.Any] - ) -> HTTPConnectionPool: + def connection_from_context(self, request_context): """ Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context. ``request_context`` must at least contain the ``scheme`` key and its value must be a key in ``key_fn_by_scheme`` instance variable. """ - if "strict" in request_context: - warnings.warn( - "The 'strict' parameter is no longer needed on Python 3+. " - "This will raise an error in urllib3 v3.0.", - FutureWarning, - ) - request_context.pop("strict") - scheme = request_context["scheme"].lower() pool_key_constructor = self.key_fn_by_scheme.get(scheme) if not pool_key_constructor: @@ -341,9 +260,7 @@ class PoolManager(RequestMethods): return self.connection_from_pool_key(pool_key, request_context=request_context) - def connection_from_pool_key( - self, pool_key: PoolKey, request_context: dict[str, typing.Any] - ) -> HTTPConnectionPool: + def connection_from_pool_key(self, pool_key, request_context=None): """ Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key. @@ -367,9 +284,7 @@ class PoolManager(RequestMethods): return pool - def connection_from_url( - self, url: str, pool_kwargs: dict[str, typing.Any] | None = None - ) -> HTTPConnectionPool: + def connection_from_url(self, url, pool_kwargs=None): """ Similar to :func:`urllib3.connectionpool.connection_from_url`. @@ -385,9 +300,7 @@ class PoolManager(RequestMethods): u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs ) - def _merge_pool_kwargs( - self, override: dict[str, typing.Any] | None - ) -> dict[str, typing.Any]: + def _merge_pool_kwargs(self, override): """ Merge a dictionary of override values for self.connection_pool_kw. @@ -407,7 +320,7 @@ class PoolManager(RequestMethods): base_pool_kwargs[key] = value return base_pool_kwargs - def _proxy_requires_url_absolute_form(self, parsed_url: Url) -> bool: + def _proxy_requires_url_absolute_form(self, parsed_url): """ Indicates if the proxy requires the complete destination URL in the request. Normally this is only needed when not using an HTTP CONNECT @@ -420,9 +333,24 @@ class PoolManager(RequestMethods): self.proxy, self.proxy_config, parsed_url.scheme ) - def urlopen( # type: ignore[override] - self, method: str, url: str, redirect: bool = True, **kw: typing.Any - ) -> BaseHTTPResponse: + def _validate_proxy_scheme_url_selection(self, url_scheme): + """ + Validates that were not attempting to do TLS in TLS connections on + Python2 or with unsupported SSL implementations. + """ + if self.proxy is None or url_scheme != "https": + return + + if self.proxy.scheme != "https": + return + + if six.PY2 and not self.proxy_config.use_forwarding_for_https: + raise ProxySchemeUnsupported( + "Contacting HTTPS destinations through HTTPS proxies " + "'via CONNECT tunnels' is not supported in Python 2" + ) + + def urlopen(self, method, url, redirect=True, **kw): """ Same as :meth:`urllib3.HTTPConnectionPool.urlopen` with custom cross-host redirect logic and only sends the request-uri @@ -432,16 +360,7 @@ class PoolManager(RequestMethods): :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. """ u = parse_url(url) - - if u.scheme is None: - warnings.warn( - "URLs without a scheme (ie 'https://') are deprecated and will raise an error " - "in urllib3 v3.0. To avoid this FutureWarning ensure all URLs " - "start with 'https://' or 'http://'. Read more in this issue: " - "https://github.com/urllib3/urllib3/issues/2920", - category=FutureWarning, - stacklevel=2, - ) + self._validate_proxy_scheme_url_selection(u.scheme) conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) @@ -449,7 +368,7 @@ class PoolManager(RequestMethods): kw["redirect"] = False if "headers" not in kw: - kw["headers"] = self.headers + kw["headers"] = self.headers.copy() if self._proxy_requires_url_absolute_form(u): response = conn.urlopen(method, url, **kw) @@ -470,7 +389,7 @@ class PoolManager(RequestMethods): kw["body"] = None kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change() - retries = kw.get("retries", response.retries) + retries = kw.get("retries") if not isinstance(retries, Retry): retries = Retry.from_int(retries, redirect=redirect) @@ -480,11 +399,10 @@ class PoolManager(RequestMethods): if retries.remove_headers_on_redirect and not conn.is_same_host( redirect_location ): - new_headers = kw["headers"].copy() - for header in kw["headers"]: + headers = list(six.iterkeys(kw["headers"])) + for header in headers: if header.lower() in retries.remove_headers_on_redirect: - new_headers.pop(header, None) - kw["headers"] = new_headers + kw["headers"].pop(header, None) try: retries = retries.increment(method, url, response=response, _pool=conn) @@ -530,53 +448,37 @@ class ProxyManager(PoolManager): private. IP address, target hostname, SNI, and port are always visible to an HTTPS proxy even when this flag is disabled. - :param proxy_assert_hostname: - The hostname of the certificate to verify against. - - :param proxy_assert_fingerprint: - The fingerprint of the certificate to verify against. - Example: - - .. code-block:: python - - import urllib3 - - proxy = urllib3.ProxyManager("https://localhost:3128/") - - resp1 = proxy.request("GET", "http://google.com/") - resp2 = proxy.request("GET", "http://httpbin.org/") - - # One pool was shared by both plain HTTP requests. - print(len(proxy.pools)) - # 1 - - resp3 = proxy.request("GET", "https://httpbin.org/") - resp4 = proxy.request("GET", "https://twitter.com/") - - # A separate pool was added for each HTTPS target. - print(len(proxy.pools)) - # 3 + >>> proxy = urllib3.ProxyManager('http://localhost:3128/') + >>> r1 = proxy.request('GET', 'http://google.com/') + >>> r2 = proxy.request('GET', 'http://httpbin.org/') + >>> len(proxy.pools) + 1 + >>> r3 = proxy.request('GET', 'https://httpbin.org/') + >>> r4 = proxy.request('GET', 'https://twitter.com/') + >>> len(proxy.pools) + 3 """ def __init__( self, - proxy_url: str, - num_pools: int = 10, - headers: typing.Mapping[str, str] | None = None, - proxy_headers: typing.Mapping[str, str] | None = None, - proxy_ssl_context: ssl.SSLContext | None = None, - use_forwarding_for_https: bool = False, - proxy_assert_hostname: None | str | typing.Literal[False] = None, - proxy_assert_fingerprint: str | None = None, - **connection_pool_kw: typing.Any, - ) -> None: + proxy_url, + num_pools=10, + headers=None, + proxy_headers=None, + proxy_ssl_context=None, + use_forwarding_for_https=False, + **connection_pool_kw + ): + if isinstance(proxy_url, HTTPConnectionPool): - str_proxy_url = f"{proxy_url.scheme}://{proxy_url.host}:{proxy_url.port}" - else: - str_proxy_url = proxy_url - proxy = parse_url(str_proxy_url) + proxy_url = "%s://%s:%i" % ( + proxy_url.scheme, + proxy_url.host, + proxy_url.port, + ) + proxy = parse_url(proxy_url) if proxy.scheme not in ("http", "https"): raise ProxySchemeUnknown(proxy.scheme) @@ -588,38 +490,25 @@ class ProxyManager(PoolManager): self.proxy = proxy self.proxy_headers = proxy_headers or {} self.proxy_ssl_context = proxy_ssl_context - self.proxy_config = ProxyConfig( - proxy_ssl_context, - use_forwarding_for_https, - proxy_assert_hostname, - proxy_assert_fingerprint, - ) + self.proxy_config = ProxyConfig(proxy_ssl_context, use_forwarding_for_https) connection_pool_kw["_proxy"] = self.proxy connection_pool_kw["_proxy_headers"] = self.proxy_headers connection_pool_kw["_proxy_config"] = self.proxy_config - super().__init__(num_pools, headers, **connection_pool_kw) + super(ProxyManager, self).__init__(num_pools, headers, **connection_pool_kw) - def connection_from_host( - self, - host: str | None, - port: int | None = None, - scheme: str | None = "http", - pool_kwargs: dict[str, typing.Any] | None = None, - ) -> HTTPConnectionPool: + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): if scheme == "https": - return super().connection_from_host( + return super(ProxyManager, self).connection_from_host( host, port, scheme, pool_kwargs=pool_kwargs ) - return super().connection_from_host( - self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs # type: ignore[union-attr] + return super(ProxyManager, self).connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs ) - def _set_proxy_headers( - self, url: str, headers: typing.Mapping[str, str] | None = None - ) -> typing.Mapping[str, str]: + def _set_proxy_headers(self, url, headers=None): """ Sets headers needed by proxies: specifically, the Accept and Host headers. Only sets headers not provided by the user. @@ -634,9 +523,7 @@ class ProxyManager(PoolManager): headers_.update(headers) return headers_ - def urlopen( # type: ignore[override] - self, method: str, url: str, redirect: bool = True, **kw: typing.Any - ) -> BaseHTTPResponse: + def urlopen(self, method, url, redirect=True, **kw): "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." u = parse_url(url) if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme): @@ -646,8 +533,8 @@ class ProxyManager(PoolManager): headers = kw.get("headers", self.headers) kw["headers"] = self._set_proxy_headers(url, headers) - return super().urlopen(method, url, redirect=redirect, **kw) + return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) -def proxy_from_url(url: str, **kw: typing.Any) -> ProxyManager: +def proxy_from_url(url, **kw): return ProxyManager(proxy_url=url, **kw) diff --git a/venv/lib/python3.12/site-packages/urllib3/py.typed b/venv/lib/python3.12/site-packages/urllib3/py.typed deleted file mode 100644 index 5f3ea3d..0000000 --- a/venv/lib/python3.12/site-packages/urllib3/py.typed +++ /dev/null @@ -1,2 +0,0 @@ -# Instruct type checkers to look for inline type annotations in this package. -# See PEP 561. diff --git a/venv/lib/python3.12/site-packages/urllib3/response.py b/venv/lib/python3.12/site-packages/urllib3/response.py index e9246b7..0bd13d4 100644 --- a/venv/lib/python3.12/site-packages/urllib3/response.py +++ b/venv/lib/python3.12/site-packages/urllib3/response.py @@ -1,38 +1,28 @@ -from __future__ import annotations +from __future__ import absolute_import -import collections import io -import json as _json import logging -import socket import sys -import typing import warnings import zlib from contextlib import contextmanager -from http.client import HTTPMessage as _HttplibHTTPMessage -from http.client import HTTPResponse as _HttplibHTTPResponse +from socket import error as SocketError from socket import timeout as SocketTimeout -if typing.TYPE_CHECKING: - from ._base_connection import BaseHTTPConnection - try: try: - import brotlicffi as brotli # type: ignore[import-not-found] + import brotlicffi as brotli except ImportError: - import brotli # type: ignore[import-not-found] + import brotli except ImportError: brotli = None from . import util -from ._base_connection import _TYPE_BODY from ._collections import HTTPHeaderDict -from .connection import BaseSSLError, HTTPConnection, HTTPException +from .connection import BaseSSLError, HTTPException from .exceptions import ( BodyNotHttplibCompatible, DecodeError, - DependencyWarning, HTTPError, IncompleteRead, InvalidChunkLength, @@ -42,262 +32,101 @@ from .exceptions import ( ResponseNotChunked, SSLError, ) +from .packages import six from .util.response import is_fp_closed, is_response_to_head -from .util.retry import Retry - -if typing.TYPE_CHECKING: - from .connectionpool import HTTPConnectionPool log = logging.getLogger(__name__) -class ContentDecoder: - def decompress(self, data: bytes, max_length: int = -1) -> bytes: - raise NotImplementedError() - - @property - def has_unconsumed_tail(self) -> bool: - raise NotImplementedError() - - def flush(self) -> bytes: - raise NotImplementedError() - - -class DeflateDecoder(ContentDecoder): - def __init__(self) -> None: +class DeflateDecoder(object): + def __init__(self): self._first_try = True - self._first_try_data = b"" - self._unfed_data = b"" + self._data = b"" self._obj = zlib.decompressobj() - def decompress(self, data: bytes, max_length: int = -1) -> bytes: - data = self._unfed_data + data - self._unfed_data = b"" - if not data and not self._obj.unconsumed_tail: + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + if not data: return data - original_max_length = max_length - if original_max_length < 0: - max_length = 0 - elif original_max_length == 0: - # We should not pass 0 to the zlib decompressor because 0 is - # the default value that will make zlib decompress without a - # length limit. - # Data should be stored for subsequent calls. - self._unfed_data = data - return b"" - # Subsequent calls always reuse `self._obj`. zlib requires - # passing the unconsumed tail if decompression is to continue. if not self._first_try: - return self._obj.decompress( - self._obj.unconsumed_tail + data, max_length=max_length - ) + return self._obj.decompress(data) - # First call tries with RFC 1950 ZLIB format. - self._first_try_data += data + self._data += data try: - decompressed = self._obj.decompress(data, max_length=max_length) + decompressed = self._obj.decompress(data) if decompressed: self._first_try = False - self._first_try_data = b"" + self._data = None return decompressed - # On failure, it falls back to RFC 1951 DEFLATE format. except zlib.error: self._first_try = False self._obj = zlib.decompressobj(-zlib.MAX_WBITS) try: - return self.decompress( - self._first_try_data, max_length=original_max_length - ) + return self.decompress(self._data) finally: - self._first_try_data = b"" - - @property - def has_unconsumed_tail(self) -> bool: - return bool(self._unfed_data) or ( - bool(self._obj.unconsumed_tail) and not self._first_try - ) - - def flush(self) -> bytes: - return self._obj.flush() + self._data = None -class GzipDecoderState: +class GzipDecoderState(object): + FIRST_MEMBER = 0 OTHER_MEMBERS = 1 SWALLOW_DATA = 2 -class GzipDecoder(ContentDecoder): - def __init__(self) -> None: +class GzipDecoder(object): + def __init__(self): self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) self._state = GzipDecoderState.FIRST_MEMBER - self._unconsumed_tail = b"" - def decompress(self, data: bytes, max_length: int = -1) -> bytes: + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): ret = bytearray() - if self._state == GzipDecoderState.SWALLOW_DATA: + if self._state == GzipDecoderState.SWALLOW_DATA or not data: return bytes(ret) - - if max_length == 0: - # We should not pass 0 to the zlib decompressor because 0 is - # the default value that will make zlib decompress without a - # length limit. - # Data should be stored for subsequent calls. - self._unconsumed_tail += data - return b"" - - # zlib requires passing the unconsumed tail to the subsequent - # call if decompression is to continue. - data = self._unconsumed_tail + data - if not data and self._obj.eof: - return bytes(ret) - while True: try: - ret += self._obj.decompress( - data, max_length=max(max_length - len(ret), 0) - ) + ret += self._obj.decompress(data) except zlib.error: previous_state = self._state # Ignore data after the first error self._state = GzipDecoderState.SWALLOW_DATA - self._unconsumed_tail = b"" if previous_state == GzipDecoderState.OTHER_MEMBERS: # Allow trailing garbage acceptable in other gzip clients return bytes(ret) raise - - self._unconsumed_tail = data = ( - self._obj.unconsumed_tail or self._obj.unused_data - ) - if max_length > 0 and len(ret) >= max_length: - break - + data = self._obj.unused_data if not data: return bytes(ret) - # When the end of a gzip member is reached, a new decompressor - # must be created for unused (possibly future) data. - if self._obj.eof: - self._state = GzipDecoderState.OTHER_MEMBERS - self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) - - return bytes(ret) - - @property - def has_unconsumed_tail(self) -> bool: - return bool(self._unconsumed_tail) - - def flush(self) -> bytes: - return self._obj.flush() + self._state = GzipDecoderState.OTHER_MEMBERS + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) if brotli is not None: - class BrotliDecoder(ContentDecoder): + class BrotliDecoder(object): # Supports both 'brotlipy' and 'Brotli' packages # since they share an import name. The top branches # are for 'brotlipy' and bottom branches for 'Brotli' - def __init__(self) -> None: + def __init__(self): self._obj = brotli.Decompressor() if hasattr(self._obj, "decompress"): - setattr(self, "_decompress", self._obj.decompress) + self.decompress = self._obj.decompress else: - setattr(self, "_decompress", self._obj.process) + self.decompress = self._obj.process - # Requires Brotli >= 1.2.0 for `output_buffer_limit`. - def _decompress(self, data: bytes, output_buffer_limit: int = -1) -> bytes: - raise NotImplementedError() - - def decompress(self, data: bytes, max_length: int = -1) -> bytes: - try: - if max_length > 0: - return self._decompress(data, output_buffer_limit=max_length) - else: - return self._decompress(data) - except TypeError: - # Fallback for Brotli/brotlicffi/brotlipy versions without - # the `output_buffer_limit` parameter. - warnings.warn( - "Brotli >= 1.2.0 is required to prevent decompression bombs.", - DependencyWarning, - ) - return self._decompress(data) - - @property - def has_unconsumed_tail(self) -> bool: - try: - return not self._obj.can_accept_more_data() - except AttributeError: - return False - - def flush(self) -> bytes: + def flush(self): if hasattr(self._obj, "flush"): - return self._obj.flush() # type: ignore[no-any-return] + return self._obj.flush() return b"" -try: - if sys.version_info >= (3, 14): - from compression import zstd - else: - from backports import zstd -except ImportError: - HAS_ZSTD = False -else: - HAS_ZSTD = True - - class ZstdDecoder(ContentDecoder): - def __init__(self) -> None: - self._obj = zstd.ZstdDecompressor() - - def decompress(self, data: bytes, max_length: int = -1) -> bytes: - if not data and not self.has_unconsumed_tail: - return b"" - if self._obj.eof: - data = self._obj.unused_data + data - self._obj = zstd.ZstdDecompressor() - part = self._obj.decompress(data, max_length=max_length) - length = len(part) - data_parts = [part] - # Every loop iteration is supposed to read data from a separate frame. - # The loop breaks when: - # - enough data is read; - # - no more unused data is available; - # - end of the last read frame has not been reached (i.e., - # more data has to be fed). - while ( - self._obj.eof - and self._obj.unused_data - and (max_length < 0 or length < max_length) - ): - unused_data = self._obj.unused_data - if not self._obj.needs_input: - self._obj = zstd.ZstdDecompressor() - part = self._obj.decompress( - unused_data, - max_length=(max_length - length) if max_length > 0 else -1, - ) - if part_length := len(part): - data_parts.append(part) - length += part_length - elif self._obj.needs_input: - break - return b"".join(data_parts) - - @property - def has_unconsumed_tail(self) -> bool: - return not (self._obj.needs_input or self._obj.eof) or bool( - self._obj.unused_data - ) - - def flush(self) -> bytes: - if not self._obj.eof: - raise DecodeError("Zstandard data is incomplete") - return b"" - - -class MultiDecoder(ContentDecoder): +class MultiDecoder(object): """ From RFC7231: If one or more encodings have been applied to a representation, the @@ -306,387 +135,32 @@ class MultiDecoder(ContentDecoder): they were applied. """ - # Maximum allowed number of chained HTTP encodings in the - # Content-Encoding header. - max_decode_links = 5 + def __init__(self, modes): + self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] - def __init__(self, modes: str) -> None: - encodings = [m.strip() for m in modes.split(",")] - if len(encodings) > self.max_decode_links: - raise DecodeError( - "Too many content encodings in the chain: " - f"{len(encodings)} > {self.max_decode_links}" - ) - self._decoders = [_get_decoder(e) for e in encodings] - - def flush(self) -> bytes: + def flush(self): return self._decoders[0].flush() - def decompress(self, data: bytes, max_length: int = -1) -> bytes: - if max_length <= 0: - for d in reversed(self._decoders): - data = d.decompress(data) - return data - - ret = bytearray() - # Every while loop iteration goes through all decoders once. - # It exits when enough data is read or no more data can be read. - # It is possible that the while loop iteration does not produce - # any data because we retrieve up to `max_length` from every - # decoder, and the amount of bytes may be insufficient for the - # next decoder to produce enough/any output. - while True: - any_data = False - for d in reversed(self._decoders): - data = d.decompress(data, max_length=max_length - len(ret)) - if data: - any_data = True - # We should not break when no data is returned because - # next decoders may produce data even with empty input. - ret += data - if not any_data or len(ret) >= max_length: - return bytes(ret) - data = b"" - - @property - def has_unconsumed_tail(self) -> bool: - return any(d.has_unconsumed_tail for d in self._decoders) + def decompress(self, data): + for d in reversed(self._decoders): + data = d.decompress(data) + return data -def _get_decoder(mode: str) -> ContentDecoder: +def _get_decoder(mode): if "," in mode: return MultiDecoder(mode) - # According to RFC 9110 section 8.4.1.3, recipients should - # consider x-gzip equivalent to gzip - if mode in ("gzip", "x-gzip"): + if mode == "gzip": return GzipDecoder() if brotli is not None and mode == "br": return BrotliDecoder() - if HAS_ZSTD and mode == "zstd": - return ZstdDecoder() - return DeflateDecoder() -class BytesQueueBuffer: - """Memory-efficient bytes buffer - - To return decoded data in read() and still follow the BufferedIOBase API, we need a - buffer to always return the correct amount of bytes. - - This buffer should be filled using calls to put() - - Our maximum memory usage is determined by the sum of the size of: - - * self.buffer, which contains the full data - * the largest chunk that we will copy in get() - """ - - def __init__(self) -> None: - self.buffer: typing.Deque[bytes | memoryview[bytes]] = collections.deque() - self._size: int = 0 - - def __len__(self) -> int: - return self._size - - def put(self, data: bytes) -> None: - self.buffer.append(data) - self._size += len(data) - - def get(self, n: int) -> bytes: - if n == 0: - return b"" - elif not self.buffer: - raise RuntimeError("buffer is empty") - elif n < 0: - raise ValueError("n should be > 0") - - if len(self.buffer[0]) == n and isinstance(self.buffer[0], bytes): - self._size -= n - return self.buffer.popleft() - - fetched = 0 - ret = io.BytesIO() - while fetched < n: - remaining = n - fetched - chunk = self.buffer.popleft() - chunk_length = len(chunk) - if remaining < chunk_length: - chunk = memoryview(chunk) - left_chunk, right_chunk = chunk[:remaining], chunk[remaining:] - ret.write(left_chunk) - self.buffer.appendleft(right_chunk) - self._size -= remaining - break - else: - ret.write(chunk) - self._size -= chunk_length - fetched += chunk_length - - if not self.buffer: - break - - return ret.getvalue() - - def get_all(self) -> bytes: - buffer = self.buffer - if not buffer: - assert self._size == 0 - return b"" - if len(buffer) == 1: - result = buffer.pop() - if isinstance(result, memoryview): - result = result.tobytes() - else: - ret = io.BytesIO() - ret.writelines(buffer.popleft() for _ in range(len(buffer))) - result = ret.getvalue() - self._size = 0 - return result - - -class BaseHTTPResponse(io.IOBase): - CONTENT_DECODERS = ["gzip", "x-gzip", "deflate"] - if brotli is not None: - CONTENT_DECODERS += ["br"] - if HAS_ZSTD: - CONTENT_DECODERS += ["zstd"] - REDIRECT_STATUSES = [301, 302, 303, 307, 308] - - DECODER_ERROR_CLASSES: tuple[type[Exception], ...] = (IOError, zlib.error) - if brotli is not None: - DECODER_ERROR_CLASSES += (brotli.error,) - - if HAS_ZSTD: - DECODER_ERROR_CLASSES += (zstd.ZstdError,) - - def __init__( - self, - *, - headers: typing.Mapping[str, str] | typing.Mapping[bytes, bytes] | None = None, - status: int, - version: int, - version_string: str, - reason: str | None, - decode_content: bool, - request_url: str | None, - retries: Retry | None = None, - ) -> None: - if isinstance(headers, HTTPHeaderDict): - self.headers = headers - else: - self.headers = HTTPHeaderDict(headers) # type: ignore[arg-type] - self.status = status - self.version = version - self.version_string = version_string - self.reason = reason - self.decode_content = decode_content - self._has_decoded_content = False - self._request_url: str | None = request_url - self.retries = retries - - self.chunked = False - tr_enc = self.headers.get("transfer-encoding", "").lower() - # Don't incur the penalty of creating a list and then discarding it - encodings = (enc.strip() for enc in tr_enc.split(",")) - if "chunked" in encodings: - self.chunked = True - - self._decoder: ContentDecoder | None = None - self.length_remaining: int | None - - def get_redirect_location(self) -> str | None | typing.Literal[False]: - """ - Should we redirect and where to? - - :returns: Truthy redirect location string if we got a redirect status - code and valid location. ``None`` if redirect status and no - location. ``False`` if not a redirect status code. - """ - if self.status in self.REDIRECT_STATUSES: - return self.headers.get("location") - return False - - @property - def data(self) -> bytes: - raise NotImplementedError() - - def json(self) -> typing.Any: - """ - Deserializes the body of the HTTP response as a Python object. - - The body of the HTTP response must be encoded using UTF-8, as per - `RFC 8529 Section 8.1 `_. - - To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to - your custom decoder instead. - - If the body of the HTTP response is not decodable to UTF-8, a - `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a - valid JSON document, a `json.JSONDecodeError` will be raised. - - Read more :ref:`here `. - - :returns: The body of the HTTP response as a Python object. - """ - data = self.data.decode("utf-8") - return _json.loads(data) - - @property - def url(self) -> str | None: - raise NotImplementedError() - - @url.setter - def url(self, url: str | None) -> None: - raise NotImplementedError() - - @property - def connection(self) -> BaseHTTPConnection | None: - raise NotImplementedError() - - @property - def retries(self) -> Retry | None: - return self._retries - - @retries.setter - def retries(self, retries: Retry | None) -> None: - # Override the request_url if retries has a redirect location. - if retries is not None and retries.history: - self.url = retries.history[-1].redirect_location - self._retries = retries - - def stream( - self, amt: int | None = 2**16, decode_content: bool | None = None - ) -> typing.Iterator[bytes]: - raise NotImplementedError() - - def read( - self, - amt: int | None = None, - decode_content: bool | None = None, - cache_content: bool = False, - ) -> bytes: - raise NotImplementedError() - - def read1( - self, - amt: int | None = None, - decode_content: bool | None = None, - ) -> bytes: - raise NotImplementedError() - - def read_chunked( - self, - amt: int | None = None, - decode_content: bool | None = None, - ) -> typing.Iterator[bytes]: - raise NotImplementedError() - - def release_conn(self) -> None: - raise NotImplementedError() - - def drain_conn(self) -> None: - raise NotImplementedError() - - def shutdown(self) -> None: - raise NotImplementedError() - - def close(self) -> None: - raise NotImplementedError() - - def _init_decoder(self) -> None: - """ - Set-up the _decoder attribute if necessary. - """ - # Note: content-encoding value should be case-insensitive, per RFC 7230 - # Section 3.2 - content_encoding = self.headers.get("content-encoding", "").lower() - if self._decoder is None: - if content_encoding in self.CONTENT_DECODERS: - self._decoder = _get_decoder(content_encoding) - elif "," in content_encoding: - encodings = [ - e.strip() - for e in content_encoding.split(",") - if e.strip() in self.CONTENT_DECODERS - ] - if encodings: - self._decoder = _get_decoder(content_encoding) - - def _decode( - self, - data: bytes, - decode_content: bool | None, - flush_decoder: bool, - max_length: int | None = None, - ) -> bytes: - """ - Decode the data passed in and potentially flush the decoder. - """ - if not decode_content: - if self._has_decoded_content: - raise RuntimeError( - "Calling read(decode_content=False) is not supported after " - "read(decode_content=True) was called." - ) - return data - - if max_length is None or flush_decoder: - max_length = -1 - - try: - if self._decoder: - data = self._decoder.decompress(data, max_length=max_length) - self._has_decoded_content = True - except self.DECODER_ERROR_CLASSES as e: - content_encoding = self.headers.get("content-encoding", "").lower() - raise DecodeError( - "Received response with content-encoding: %s, but " - "failed to decode it." % content_encoding, - e, - ) from e - if flush_decoder: - data += self._flush_decoder() - - return data - - def _flush_decoder(self) -> bytes: - """ - Flushes the decoder. Should only be called if the decoder is actually - being used. - """ - if self._decoder: - return self._decoder.decompress(b"") + self._decoder.flush() - return b"" - - # Compatibility methods for `io` module - def readinto(self, b: bytearray | memoryview[int]) -> int: - temp = self.read(len(b)) - if len(temp) == 0: - return 0 - else: - b[: len(temp)] = temp - return len(temp) - - # Methods used by dependent libraries - def getheaders(self) -> HTTPHeaderDict: - return self.headers - - def getheader(self, name: str, default: str | None = None) -> str | None: - return self.headers.get(name, default) - - # Compatibility method for http.cookiejar - def info(self) -> HTTPHeaderDict: - return self.headers - - def geturl(self) -> str | None: - return self.url - - -class HTTPResponse(BaseHTTPResponse): +class HTTPResponse(io.IOBase): """ HTTP Response container. @@ -719,128 +193,140 @@ class HTTPResponse(BaseHTTPResponse): value of Content-Length header, if present. Otherwise, raise error. """ + CONTENT_DECODERS = ["gzip", "deflate"] + if brotli is not None: + CONTENT_DECODERS += ["br"] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + def __init__( self, - body: _TYPE_BODY = "", - headers: typing.Mapping[str, str] | typing.Mapping[bytes, bytes] | None = None, - status: int = 0, - version: int = 0, - version_string: str = "HTTP/?", - reason: str | None = None, - preload_content: bool = True, - decode_content: bool = True, - original_response: _HttplibHTTPResponse | None = None, - pool: HTTPConnectionPool | None = None, - connection: HTTPConnection | None = None, - msg: _HttplibHTTPMessage | None = None, - retries: Retry | None = None, - enforce_content_length: bool = True, - request_method: str | None = None, - request_url: str | None = None, - auto_close: bool = True, - sock_shutdown: typing.Callable[[int], None] | None = None, - ) -> None: - super().__init__( - headers=headers, - status=status, - version=version, - version_string=version_string, - reason=reason, - decode_content=decode_content, - request_url=request_url, - retries=retries, - ) + body="", + headers=None, + status=0, + version=0, + reason=None, + strict=0, + preload_content=True, + decode_content=True, + original_response=None, + pool=None, + connection=None, + msg=None, + retries=None, + enforce_content_length=False, + request_method=None, + request_url=None, + auto_close=True, + ): + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) + self.status = status + self.version = version + self.reason = reason + self.strict = strict + self.decode_content = decode_content + self.retries = retries self.enforce_content_length = enforce_content_length self.auto_close = auto_close + self._decoder = None self._body = None - self._uncached_read_occurred = False - self._fp: _HttplibHTTPResponse | None = None + self._fp = None self._original_response = original_response self._fp_bytes_read = 0 self.msg = msg + self._request_url = request_url - if body and isinstance(body, (str, bytes)): + if body and isinstance(body, (six.string_types, bytes)): self._body = body self._pool = pool self._connection = connection if hasattr(body, "read"): - self._fp = body # type: ignore[assignment] - self._sock_shutdown = sock_shutdown + self._fp = body # Are we using the chunked-style of transfer encoding? - self.chunk_left: int | None = None + self.chunked = False + self.chunk_left = None + tr_enc = self.headers.get("transfer-encoding", "").lower() + # Don't incur the penalty of creating a list and then discarding it + encodings = (enc.strip() for enc in tr_enc.split(",")) + if "chunked" in encodings: + self.chunked = True # Determine length of response self.length_remaining = self._init_length(request_method) - # Used to return the correct amount of bytes for partial read()s - self._decoded_buffer = BytesQueueBuffer() - # If requested, preload the body. if preload_content and not self._body: self._body = self.read(decode_content=decode_content) - def release_conn(self) -> None: + def get_redirect_location(self): + """ + Should we redirect and where to? + + :returns: Truthy redirect location string if we got a redirect status + code and valid location. ``None`` if redirect status and no + location. ``False`` if not a redirect status code. + """ + if self.status in self.REDIRECT_STATUSES: + return self.headers.get("location") + + return False + + def release_conn(self): if not self._pool or not self._connection: - return None + return self._pool._put_conn(self._connection) self._connection = None - def drain_conn(self) -> None: + def drain_conn(self): """ Read and discard any remaining HTTP response data in the response connection. Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. """ try: - self._raw_read() - except (HTTPError, OSError, BaseSSLError, HTTPException): + self.read() + except (HTTPError, SocketError, BaseSSLError, HTTPException): pass - if self._has_decoded_content: - # `_raw_read` skips decompression, so we should clean up the - # decoder to avoid keeping unnecessary data in memory. - self._decoded_buffer = BytesQueueBuffer() - self._decoder = None @property - def data(self) -> bytes: + def data(self): # For backwards-compat with earlier urllib3 0.4 and earlier. if self._body: - return self._body # type: ignore[return-value] + return self._body if self._fp: return self.read(cache_content=True) - return None # type: ignore[return-value] - @property - def connection(self) -> HTTPConnection | None: + def connection(self): return self._connection - def isclosed(self) -> bool: + def isclosed(self): return is_fp_closed(self._fp) - def tell(self) -> int: + def tell(self): """ Obtain the number of bytes pulled over the wire so far. May differ from - the amount of content returned by :meth:`HTTPResponse.read` + the amount of content returned by :meth:``urllib3.response.HTTPResponse.read`` if bytes are encoded on the wire (e.g, compressed). """ return self._fp_bytes_read - def _init_length(self, request_method: str | None) -> int | None: + def _init_length(self, request_method): """ Set initial length value for Response content if available. """ - length: int | None - content_length: str | None = self.headers.get("content-length") + length = self.headers.get("content-length") - if content_length is not None: + if length is not None: if self.chunked: # This Response will fail with an IncompleteRead if it can't be # received as chunked. This method falls back to attempt reading @@ -860,11 +346,11 @@ class HTTPResponse(BaseHTTPResponse): # (e.g. Content-Length: 42, 42). This line ensures the values # are all valid ints and that as long as the `set` length is 1, # all values are the same. Otherwise, the header is invalid. - lengths = {int(val) for val in content_length.split(",")} + lengths = set([int(val) for val in length.split(",")]) if len(lengths) > 1: raise InvalidHeader( "Content-Length contained multiple " - "unmatching values (%s)" % content_length + "unmatching values (%s)" % length ) length = lengths.pop() except ValueError: @@ -873,9 +359,6 @@ class HTTPResponse(BaseHTTPResponse): if length < 0: length = None - else: # if content_length is None - length = None - # Convert status to int for comparison # In some cases, httplib returns a status of "_UNKNOWN" try: @@ -889,8 +372,64 @@ class HTTPResponse(BaseHTTPResponse): return length + def _init_decoder(self): + """ + Set-up the _decoder attribute if necessary. + """ + # Note: content-encoding value should be case-insensitive, per RFC 7230 + # Section 3.2 + content_encoding = self.headers.get("content-encoding", "").lower() + if self._decoder is None: + if content_encoding in self.CONTENT_DECODERS: + self._decoder = _get_decoder(content_encoding) + elif "," in content_encoding: + encodings = [ + e.strip() + for e in content_encoding.split(",") + if e.strip() in self.CONTENT_DECODERS + ] + if len(encodings): + self._decoder = _get_decoder(content_encoding) + + DECODER_ERROR_CLASSES = (IOError, zlib.error) + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + + def _decode(self, data, decode_content, flush_decoder): + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + return data + + try: + if self._decoder: + data = self._decoder.decompress(data) + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( + "Received response with content-encoding: %s, but " + "failed to decode it." % content_encoding, + e, + ) + if flush_decoder: + data += self._flush_decoder() + + return data + + def _flush_decoder(self): + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + buf = self._decoder.decompress(b"") + return buf + self._decoder.flush() + + return b"" + @contextmanager - def _error_catcher(self) -> typing.Generator[None]: + def _error_catcher(self): """ Catch low-level python exceptions, instead re-raising urllib3 variants, so that low-level exceptions are not leaked in the @@ -904,28 +443,22 @@ class HTTPResponse(BaseHTTPResponse): try: yield - except SocketTimeout as e: + except SocketTimeout: # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but # there is yet no clean way to get at it from this context. - raise ReadTimeoutError(self._pool, None, "Read timed out.") from e # type: ignore[arg-type] + raise ReadTimeoutError(self._pool, None, "Read timed out.") except BaseSSLError as e: - # SSL errors related to framing/MAC get wrapped and reraised here - raise SSLError(e) from e + # FIXME: Is there a better way to differentiate between SSLErrors? + if "read operation timed out" not in str(e): + # SSL errors related to framing/MAC get wrapped and reraised here + raise SSLError(e) - except IncompleteRead as e: - if ( - e.expected is not None - and e.partial is not None - and e.expected == -e.partial - ): - arg = "Response may not contain content." - else: - arg = f"Connection broken: {e!r}" - raise ProtocolError(arg, e) from e + raise ReadTimeoutError(self._pool, None, "Read timed out.") - except (HTTPException, OSError) as e: - raise ProtocolError(f"Connection broken: {e!r}", e) from e + except (HTTPException, SocketError) as e: + # This includes IncompleteRead. + raise ProtocolError("Connection broken: %r" % e, e) # If no exception is thrown, we should avoid cleaning up # unnecessarily. @@ -951,12 +484,7 @@ class HTTPResponse(BaseHTTPResponse): if self._original_response and self._original_response.isclosed(): self.release_conn() - def _fp_read( - self, - amt: int | None = None, - *, - read1: bool = False, - ) -> bytes: + def _fp_read(self, amt): """ Read a response with the thought that reading the number of bytes larger than can fit in a 32-bit int at a time via SSL in some @@ -964,20 +492,22 @@ class HTTPResponse(BaseHTTPResponse): if `amt` or `self.length_remaining` indicate that a problem may happen. - This happens to urllib3 injected with pyOpenSSL-backed SSL-support. + The known cases: + * 3.8 <= CPython < 3.9.7 because of a bug + https://github.com/urllib3/urllib3/issues/2513#issuecomment-1152559900. + * urllib3 injected with pyOpenSSL-backed SSL-support. + * CPython < 3.10 only when `amt` does not fit 32-bit int. """ assert self._fp - c_int_max = 2**31 - 1 + c_int_max = 2 ** 31 - 1 if ( - (amt and amt > c_int_max) - or ( - amt is None - and self.length_remaining - and self.length_remaining > c_int_max + ( + (amt and amt > c_int_max) + or (self.length_remaining and self.length_remaining > c_int_max) ) - ) and util.IS_PYOPENSSL: - if read1: - return self._fp.read1(c_int_max) + and not util.IS_SECURETRANSPORT + and (util.IS_PYOPENSSL or sys.version_info < (3, 10)) + ): buffer = io.BytesIO() # Besides `max_chunk_amt` being a maximum chunk size, it # affects memory overhead of reading a response by this @@ -985,7 +515,7 @@ class HTTPResponse(BaseHTTPResponse): # `c_int_max` equal to 2 GiB - 1 byte is the actual maximum # chunk size that does not lead to an overflow error, but # 256 MiB is a compromise. - max_chunk_amt = 2**28 + max_chunk_amt = 2 ** 28 while amt is None or amt != 0: if amt is not None: chunk_amt = min(amt, max_chunk_amt) @@ -998,70 +528,11 @@ class HTTPResponse(BaseHTTPResponse): buffer.write(data) del data # to reduce peak memory usage by `max_chunk_amt`. return buffer.getvalue() - elif read1: - return self._fp.read1(amt) if amt is not None else self._fp.read1() else: # StringIO doesn't like amt=None return self._fp.read(amt) if amt is not None else self._fp.read() - def _raw_read( - self, - amt: int | None = None, - *, - read1: bool = False, - ) -> bytes: - """ - Reads `amt` of bytes from the socket. - """ - if self._fp is None: - return None # type: ignore[return-value] - - fp_closed = getattr(self._fp, "closed", False) - - with self._error_catcher(): - data = self._fp_read(amt, read1=read1) if not fp_closed else b"" - if amt is not None and amt != 0 and not data: - # Platform-specific: Buggy versions of Python. - # Close the connection when no data is returned - # - # This is redundant to what httplib/http.client _should_ - # already do. However, versions of python released before - # December 15, 2012 (http://bugs.python.org/issue16298) do - # not properly close the connection in all cases. There is - # no harm in redundantly calling close. - self._fp.close() - if ( - self.enforce_content_length - and self.length_remaining is not None - and self.length_remaining != 0 - ): - # This is an edge case that httplib failed to cover due - # to concerns of backward compatibility. We're - # addressing it here to make sure IncompleteRead is - # raised during streaming, so all calls with incorrect - # Content-Length are caught. - raise IncompleteRead(self._fp_bytes_read, self.length_remaining) - elif read1 and ( - (amt != 0 and not data) or self.length_remaining == len(data) - ): - # All data has been read, but `self._fp.read1` in - # CPython 3.12 and older doesn't always close - # `http.client.HTTPResponse`, so we close it here. - # See https://github.com/python/cpython/issues/113199 - self._fp.close() - - if data: - self._fp_bytes_read += len(data) - if self.length_remaining is not None: - self.length_remaining -= len(data) - return data - - def read( - self, - amt: int | None = None, - decode_content: bool | None = None, - cache_content: bool = False, - ) -> bytes: + def read(self, amt=None, decode_content=None, cache_content=False): """ Similar to :meth:`http.client.HTTPResponse.read`, but with two additional parameters: ``decode_content`` and ``cache_content``. @@ -1086,158 +557,54 @@ class HTTPResponse(BaseHTTPResponse): if decode_content is None: decode_content = self.decode_content - if amt and amt < 0: - # Negative numbers and `None` should be treated the same. - amt = None - elif amt is not None: - cache_content = False + if self._fp is None: + return - if ( - self._decoder - and self._decoder.has_unconsumed_tail - and len(self._decoded_buffer) < amt - ): - decoded_data = self._decode( - b"", - decode_content, - flush_decoder=False, - max_length=amt - len(self._decoded_buffer), - ) - self._decoded_buffer.put(decoded_data) - if len(self._decoded_buffer) >= amt: - return self._decoded_buffer.get(amt) + flush_decoder = False + fp_closed = getattr(self._fp, "closed", False) - data = self._raw_read(amt) - if not cache_content: - self._uncached_read_occurred = True + with self._error_catcher(): + data = self._fp_read(amt) if not fp_closed else b"" + if amt is None: + flush_decoder = True + else: + cache_content = False + if ( + amt != 0 and not data + ): # Platform-specific: Buggy versions of Python. + # Close the connection when no data is returned + # + # This is redundant to what httplib/http.client _should_ + # already do. However, versions of python released before + # December 15, 2012 (http://bugs.python.org/issue16298) do + # not properly close the connection in all cases. There is + # no harm in redundantly calling close. + self._fp.close() + flush_decoder = True + if self.enforce_content_length and self.length_remaining not in ( + 0, + None, + ): + # This is an edge case that httplib failed to cover due + # to concerns of backward compatibility. We're + # addressing it here to make sure IncompleteRead is + # raised during streaming, so all calls with incorrect + # Content-Length are caught. + raise IncompleteRead(self._fp_bytes_read, self.length_remaining) - flush_decoder = amt is None or (amt != 0 and not data) + if data: + self._fp_bytes_read += len(data) + if self.length_remaining is not None: + self.length_remaining -= len(data) - if ( - not data - and len(self._decoded_buffer) == 0 - and not (self._decoder and self._decoder.has_unconsumed_tail) - ): - return data - - if amt is None: data = self._decode(data, decode_content, flush_decoder) - # It's possible that there is buffered decoded data after a - # partial read. - if decode_content and len(self._decoded_buffer) > 0: - self._decoded_buffer.put(data) - data = self._decoded_buffer.get_all() - if cache_content and not self._uncached_read_occurred: + if cache_content: self._body = data - else: - # do not waste memory on buffer when not decoding - if not decode_content: - if self._has_decoded_content: - raise RuntimeError( - "Calling read(decode_content=False) is not supported after " - "read(decode_content=True) was called." - ) - return data - - decoded_data = self._decode( - data, - decode_content, - flush_decoder, - max_length=amt - len(self._decoded_buffer), - ) - self._decoded_buffer.put(decoded_data) - - while len(self._decoded_buffer) < amt and data: - # TODO make sure to initially read enough data to get past the headers - # For example, the GZ file header takes 10 bytes, we don't want to read - # it one byte at a time - data = self._raw_read(amt) - decoded_data = self._decode( - data, - decode_content, - flush_decoder, - max_length=amt - len(self._decoded_buffer), - ) - self._decoded_buffer.put(decoded_data) - data = self._decoded_buffer.get(amt) return data - def read1( - self, - amt: int | None = None, - decode_content: bool | None = None, - ) -> bytes: - """ - Similar to ``http.client.HTTPResponse.read1`` and documented - in :meth:`io.BufferedReader.read1`, but with an additional parameter: - ``decode_content``. - - :param amt: - How much of the content to read. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - """ - if decode_content is None: - decode_content = self.decode_content - if amt and amt < 0: - # Negative numbers and `None` should be treated the same. - amt = None - # try and respond without going to the network - if self._has_decoded_content: - if not decode_content: - raise RuntimeError( - "Calling read1(decode_content=False) is not supported after " - "read1(decode_content=True) was called." - ) - if ( - self._decoder - and self._decoder.has_unconsumed_tail - and (amt is None or len(self._decoded_buffer) < amt) - ): - decoded_data = self._decode( - b"", - decode_content, - flush_decoder=False, - max_length=( - amt - len(self._decoded_buffer) if amt is not None else None - ), - ) - self._decoded_buffer.put(decoded_data) - if len(self._decoded_buffer) > 0: - if amt is None: - return self._decoded_buffer.get_all() - return self._decoded_buffer.get(amt) - if amt == 0: - return b"" - - # FIXME, this method's type doesn't say returning None is possible - data = self._raw_read(amt, read1=True) - self._uncached_read_occurred = True - if not decode_content or data is None: - return data - - self._init_decoder() - while True: - flush_decoder = not data - decoded_data = self._decode( - data, decode_content, flush_decoder, max_length=amt - ) - self._decoded_buffer.put(decoded_data) - if decoded_data or flush_decoder: - break - data = self._raw_read(8192, read1=True) - - if amt is None: - return self._decoded_buffer.get_all() - return self._decoded_buffer.get(amt) - - def stream( - self, amt: int | None = 2**16, decode_content: bool | None = None - ) -> typing.Generator[bytes]: + def stream(self, amt=2 ** 16, decode_content=None): """ A generator wrapper for the read() method. A call will block until ``amt`` bytes have been read from the connection or until the @@ -1253,39 +620,74 @@ class HTTPResponse(BaseHTTPResponse): If True, will attempt to decode the body based on the 'content-encoding' header. """ - if amt == 0: - return - if self.chunked and self.supports_chunked_reads(): - yield from self.read_chunked(amt, decode_content=decode_content) + for line in self.read_chunked(amt, decode_content=decode_content): + yield line else: - while ( - not is_fp_closed(self._fp) - or len(self._decoded_buffer) > 0 - or (self._decoder and self._decoder.has_unconsumed_tail) - ): + while not is_fp_closed(self._fp): data = self.read(amt=amt, decode_content=decode_content) if data: yield data + @classmethod + def from_httplib(ResponseCls, r, **response_kw): + """ + Given an :class:`http.client.HTTPResponse` instance ``r``, return a + corresponding :class:`urllib3.response.HTTPResponse` object. + + Remaining parameters are passed to the HTTPResponse constructor, along + with ``original_response=r``. + """ + headers = r.msg + + if not isinstance(headers, HTTPHeaderDict): + if six.PY2: + # Python 2.7 + headers = HTTPHeaderDict.from_httplib(headers) + else: + headers = HTTPHeaderDict(headers.items()) + + # HTTPResponse objects in Python 3 don't have a .strict attribute + strict = getattr(r, "strict", 0) + resp = ResponseCls( + body=r, + headers=headers, + status=r.status, + version=r.version, + reason=r.reason, + strict=strict, + original_response=r, + **response_kw + ) + return resp + + # Backwards-compatibility methods for http.client.HTTPResponse + def getheaders(self): + warnings.warn( + "HTTPResponse.getheaders() is deprecated and will be removed " + "in urllib3 v2.1.0. Instead access HTTPResponse.headers directly.", + category=DeprecationWarning, + stacklevel=2, + ) + return self.headers + + def getheader(self, name, default=None): + warnings.warn( + "HTTPResponse.getheader() is deprecated and will be removed " + "in urllib3 v2.1.0. Instead use HTTPResponse.headers.get(name, default).", + category=DeprecationWarning, + stacklevel=2, + ) + return self.headers.get(name, default) + + # Backwards compatibility for http.cookiejar + def info(self): + return self.headers + # Overrides from io.IOBase - def readable(self) -> bool: - return True - - def shutdown(self) -> None: - if not self._sock_shutdown: - raise ValueError("Cannot shutdown socket as self._sock_shutdown is not set") - if self._connection is None: - raise RuntimeError( - "Cannot shutdown as connection has already been released to the pool" - ) - self._sock_shutdown(socket.SHUT_RD) - - def close(self) -> None: - self._sock_shutdown = None - - if not self.closed and self._fp: + def close(self): + if not self.closed: self._fp.close() if self._connection: @@ -1295,9 +697,9 @@ class HTTPResponse(BaseHTTPResponse): io.IOBase.close(self) @property - def closed(self) -> bool: + def closed(self): if not self.auto_close: - return io.IOBase.closed.__get__(self) # type: ignore[no-any-return] + return io.IOBase.closed.__get__(self) elif self._fp is None: return True elif hasattr(self._fp, "isclosed"): @@ -1307,18 +709,18 @@ class HTTPResponse(BaseHTTPResponse): else: return True - def fileno(self) -> int: + def fileno(self): if self._fp is None: - raise OSError("HTTPResponse has no file to get a fileno from") + raise IOError("HTTPResponse has no file to get a fileno from") elif hasattr(self._fp, "fileno"): return self._fp.fileno() else: - raise OSError( + raise IOError( "The file-like object this HTTPResponse is wrapped " "around has no file descriptor" ) - def flush(self) -> None: + def flush(self): if ( self._fp is not None and hasattr(self._fp, "flush") @@ -1326,7 +728,20 @@ class HTTPResponse(BaseHTTPResponse): ): return self._fp.flush() - def supports_chunked_reads(self) -> bool: + def readable(self): + # This method is required for `io` module compatibility. + return True + + def readinto(self, b): + # This method is required for `io` module compatibility. + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[: len(temp)] = temp + return len(temp) + + def supports_chunked_reads(self): """ Checks if the underlying file-like object looks like a :class:`http.client.HTTPResponse` object. We do this by testing for @@ -1335,49 +750,43 @@ class HTTPResponse(BaseHTTPResponse): """ return hasattr(self._fp, "fp") - def _update_chunk_length(self) -> None: + def _update_chunk_length(self): # First, we'll figure out length of a chunk and then # we'll try to read it from socket. if self.chunk_left is not None: - return None - line = self._fp.fp.readline() # type: ignore[union-attr] + return + line = self._fp.fp.readline() line = line.split(b";", 1)[0] try: self.chunk_left = int(line, 16) except ValueError: + # Invalid chunked protocol response, abort. self.close() - if line: - # Invalid chunked protocol response, abort. - raise InvalidChunkLength(self, line) from None - else: - # Truncated at start of next chunk - raise ProtocolError("Response ended prematurely") from None + raise InvalidChunkLength(self, line) - def _handle_chunk(self, amt: int | None) -> bytes: + def _handle_chunk(self, amt): returned_chunk = None if amt is None: - chunk = self._fp._safe_read(self.chunk_left) # type: ignore[union-attr] + chunk = self._fp._safe_read(self.chunk_left) returned_chunk = chunk - self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. self.chunk_left = None - elif self.chunk_left is not None and amt < self.chunk_left: - value = self._fp._safe_read(amt) # type: ignore[union-attr] + elif amt < self.chunk_left: + value = self._fp._safe_read(amt) self.chunk_left = self.chunk_left - amt returned_chunk = value elif amt == self.chunk_left: - value = self._fp._safe_read(amt) # type: ignore[union-attr] - self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + value = self._fp._safe_read(amt) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. self.chunk_left = None returned_chunk = value else: # amt > self.chunk_left - returned_chunk = self._fp._safe_read(self.chunk_left) # type: ignore[union-attr] - self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + returned_chunk = self._fp._safe_read(self.chunk_left) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. self.chunk_left = None - return returned_chunk # type: ignore[no-any-return] + return returned_chunk - def read_chunked( - self, amt: int | None = None, decode_content: bool | None = None - ) -> typing.Generator[bytes]: + def read_chunked(self, amt=None, decode_content=None): """ Similar to :meth:`HTTPResponse.read`, but with an additional parameter: ``decode_content``. @@ -1408,35 +817,20 @@ class HTTPResponse(BaseHTTPResponse): # Don't bother reading the body of a HEAD request. if self._original_response and is_response_to_head(self._original_response): self._original_response.close() - return None + return # If a response is already read and closed # then return immediately. - if self._fp.fp is None: # type: ignore[union-attr] - return None - - if amt == 0: + if self._fp.fp is None: return - elif amt and amt < 0: - # Negative numbers and `None` should be treated the same, - # but httplib handles only `None` correctly. - amt = None while True: - # First, check if any data is left in the decoder's buffer. - if self._decoder and self._decoder.has_unconsumed_tail: - chunk = b"" - else: - self._update_chunk_length() - self._uncached_read_occurred = True - if self.chunk_left == 0: - break - chunk = self._handle_chunk(amt) + self._update_chunk_length() + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) decoded = self._decode( - chunk, - decode_content=decode_content, - flush_decoder=False, - max_length=amt, + chunk, decode_content=decode_content, flush_decoder=False ) if decoded: yield decoded @@ -1450,7 +844,7 @@ class HTTPResponse(BaseHTTPResponse): yield decoded # Chunk content ends with \r\n: discard it. - while self._fp is not None: + while True: line = self._fp.fp.readline() if not line: # Some sites may not end with '\r\n'. @@ -1462,29 +856,27 @@ class HTTPResponse(BaseHTTPResponse): if self._original_response: self._original_response.close() - @property - def url(self) -> str | None: + def geturl(self): """ Returns the URL that was the source of this response. If the request that generated this response redirected, this method will return the final redirect location. """ - return self._request_url + if self.retries is not None and len(self.retries.history): + return self.retries.history[-1].redirect_location + else: + return self._request_url - @url.setter - def url(self, url: str | None) -> None: - self._request_url = url - - def __iter__(self) -> typing.Iterator[bytes]: - buffer: list[bytes] = [] + def __iter__(self): + buffer = [] for chunk in self.stream(decode_content=True): if b"\n" in chunk: - chunks = chunk.split(b"\n") - yield b"".join(buffer) + chunks[0] + b"\n" - for x in chunks[1:-1]: + chunk = chunk.split(b"\n") + yield b"".join(buffer) + chunk[0] + b"\n" + for x in chunk[1:-1]: yield x + b"\n" - if chunks[-1]: - buffer = [chunks[-1]] + if chunk[-1]: + buffer = [chunk[-1]] else: buffer = [] else: diff --git a/venv/lib/python3.12/site-packages/urllib3/util/__init__.py b/venv/lib/python3.12/site-packages/urllib3/util/__init__.py index 5341260..4547fc5 100644 --- a/venv/lib/python3.12/site-packages/urllib3/util/__init__.py +++ b/venv/lib/python3.12/site-packages/urllib3/util/__init__.py @@ -1,39 +1,46 @@ -# For backwards compatibility, provide imports that used to be here. -from __future__ import annotations +from __future__ import absolute_import +# For backwards compatibility, provide imports that used to be here. from .connection import is_connection_dropped from .request import SKIP_HEADER, SKIPPABLE_HEADERS, make_headers from .response import is_fp_closed from .retry import Retry from .ssl_ import ( ALPN_PROTOCOLS, + HAS_SNI, IS_PYOPENSSL, + IS_SECURETRANSPORT, + PROTOCOL_TLS, SSLContext, assert_fingerprint, - create_urllib3_context, resolve_cert_reqs, resolve_ssl_version, ssl_wrap_socket, ) -from .timeout import Timeout -from .url import Url, parse_url +from .timeout import Timeout, current_time +from .url import Url, get_host, parse_url, split_first from .wait import wait_for_read, wait_for_write __all__ = ( + "HAS_SNI", "IS_PYOPENSSL", + "IS_SECURETRANSPORT", "SSLContext", + "PROTOCOL_TLS", "ALPN_PROTOCOLS", "Retry", "Timeout", "Url", "assert_fingerprint", - "create_urllib3_context", + "current_time", "is_connection_dropped", "is_fp_closed", + "get_host", "parse_url", "make_headers", "resolve_cert_reqs", "resolve_ssl_version", + "split_first", "ssl_wrap_socket", "wait_for_read", "wait_for_write", diff --git a/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/__init__.cpython-312.pyc index 37e3133eb43dea892782e72d3b95b0e30b5e42d5..299564ebbe7fdc81758a4c1c188575c0e8ca300b 100644 GIT binary patch delta 568 zcmYMwPm9w)6aesFR2`>9)IWE4WrcZwnqggi>&&B%#S3oP$Hxjk}SIDU&JU zK~x0qC11erpx?ricpE77>TN-m-o$xZ{|w9z-ecyyH?uT;m1|#YyNcBMWIuWSzBH>f zboBG{Y%e=vby8;y(!l6gJ1(c2%pneIlIBHRNw-*wv{;+8**4i`J7nh~Urn#GU9!tM zq@!Y!bYEFL`v=tTVYxm&%CmGTVw$i?#>MRW>7%Nwxx>Jt!N`yNzCR45@z4!v=p9Jw zAf&-#KNxsnIFuVI3J3ke(I6VRUKseJNLqhy(P$VJC%995#P?lI8d1XHY$~Mnbjo@B zLQtU+y^_=qc`C~%v7o0}E@Wjg=6Ot~Drx4EG!gV!!gEoaunw32 zd>XFw>(!ooj4op$&8 zGAGh#u~MsQwPU5B94eJM4IkCD+Nn}2RqOSN|BBaTxk(J&Gqet|Ec0wnYr2kS-1_hM zAd9KcFm1!_cm6VumH>fK_CH$z#^~& zECVu-1m*#OQ7x>|kfxn?ybdumjqvP_hFFJ=(PTsVzud((6}(xG)4G&rbIgiEAsV>R zP47Ro|H40(H`y&_yS>jC@fhO)$`8;^9~D1Qv4`?Kl$vls%uNtu41C2AUhSh?AMK6; Mafy2n$OilT2ScB7^8f$< diff --git a/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/connection.cpython-312.pyc b/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/connection.cpython-312.pyc index 45d78564a175165b29be16b51e48d681d3e6ac33..02184d3065a9e60b93027531d324f7bdb6c9f35b 100644 GIT binary patch delta 2419 zcmZ8iZ)_V$6`$EZ_Ih{i^}jT|I88P*xz=e&TMArpLwo5Zxt<7V;ZmU*bk4@RNt~{| z*6jMyluhj3-G>$-QnZNn144X22oxkFD!y~*4xMygQd+6TY6a+y5B-8d5~Qji!JBn9 zDL2}E^XAQ)_ujnu&6~gV-yNub9F2+y+P&yoivuZyKB0r4vAfLnkAb;^bfjYwm2n